diff --git a/.screens/ff8-worldmap-official-release-vs-ffnx.png b/.screens/ff8-worldmap-official-release-vs-ffnx.png new file mode 100644 index 00000000..8debdba0 Binary files /dev/null and b/.screens/ff8-worldmap-official-release-vs-ffnx.png differ diff --git a/Changelog.md b/Changelog.md index cc169ad4..4e22bb89 100644 --- a/Changelog.md +++ b/Changelog.md @@ -21,8 +21,12 @@ - Common: Fix startup hang on launch - Common: Fix jp version crash +- Config: enable worldmap fixes by default - Graphics: Add Field texture replacement ( https://github.com/julianxhokaxhiu/FFNx/pull/542 https://github.com/julianxhokaxhiu/FFNx/pull/545 ) +- Graphics: Add Battle texture replacement ( https://github.com/julianxhokaxhiu/FFNx/pull/564 ) - Graphics: Fix wrong texture replacements in battle +- Graphics: Fix bad texture UVs in worldmap ( https://github.com/julianxhokaxhiu/FFNx/pull/564 ) +- Graphics: Disable texture filtering for external textures, as sprites can overlap or look bad in FF8 # 1.15.0 diff --git a/README.md b/README.md index 70e7f36a..6ed39af1 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ We are always open for contributions via PRs, and in case you want to join the c #### FF8 - Vibration support - **Voice acting** -- Maki's patch for worldmap included +- Various graphical patches for worldmap included - Enable the VRAM debug window while playing in order to see how the engine uploads textures ### As a modder @@ -102,10 +102,11 @@ For a more in-depth documentation feel free to visit the [docs/](docs/) folder. ## Screenshots -| Vanilla/Steam | -| :-------------------------------------------------------: | -| ![Final Fantasy VII running on Vulkan](.screens/ff7.png) | -| ![Final Fantasy VIII running on Vulkan](.screens/ff8.png) | +| Vanilla/Steam | +| :--------------------------------------------------------------------------------------------------: | +| ![Final Fantasy VII running on Vulkan](.screens/ff7.png) | +| ![Final Fantasy VIII running on Vulkan](.screens/ff8.png) | +| [![Final Fantasy VIII Worldmap graphical patches](.screens/ff8-worldmap-official-release-vs-ffnx.png)](https://imgsli.com/MTg5NjQ2) | ## Tech Stack diff --git a/misc/FFNx.toml b/misc/FFNx.toml index d0b2d79a..f4490894 100644 --- a/misc/FFNx.toml +++ b/misc/FFNx.toml @@ -595,6 +595,10 @@ save_path = "save" #~~~~~~~~~~~~~~~~~~~~~~~~~~~ ff8_worldmap_internal_highres_textures = true +# Attempt to fix texturing holes by restoring full precision from the game data +#~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ff8_fix_uv_coords_precision = true + ## GAME INSTALLATION OPTIONS #[APP PATH] diff --git a/src/cfg.cpp b/src/cfg.cpp index b6ee2940..e4e0c8fa 100644 --- a/src/cfg.cpp +++ b/src/cfg.cpp @@ -128,6 +128,7 @@ double hdr_max_nits; long external_audio_number_of_channels; long external_audio_sample_rate; bool ff8_worldmap_internal_highres_textures; +bool ff8_fix_uv_coords_precision; std::string app_path; std::string data_drive; @@ -271,7 +272,8 @@ void read_cfg() hdr_max_nits = config["hdr_max_nits"].value_or(0); external_audio_number_of_channels = config["external_audio_number_of_channels"].value_or(2); external_audio_sample_rate = config["external_audio_sample_rate"].value_or(44100); - ff8_worldmap_internal_highres_textures = config["ff8_worldmap_internal_highres_textures"].value_or(false); + ff8_worldmap_internal_highres_textures = config["ff8_worldmap_internal_highres_textures"].value_or(true); + ff8_fix_uv_coords_precision = config["ff8_fix_uv_coords_precision"].value_or(true); app_path = config["app_path"].value_or(""); data_drive = config["data_drive"].value_or(""); diff --git a/src/cfg.h b/src/cfg.h index a95e92d9..643ca1b4 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -141,6 +141,7 @@ extern double hdr_max_nits; extern long external_audio_number_of_channels; extern long external_audio_sample_rate; extern bool ff8_worldmap_internal_highres_textures; +extern bool ff8_fix_uv_coords_precision; extern std::string app_path; extern std::string data_drive; diff --git a/src/common.cpp b/src/common.cpp index fa0dda13..5fb77e2b 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -61,6 +61,7 @@ #include "ff8/vram.h" #include "ff8/vibration.h" #include "ff8/engine.h" +#include "ff8/uv_patch.h" bool proxyWndProc = false; @@ -866,6 +867,9 @@ int common_create_window(HINSTANCE hInstance, struct game_obj* game_object) if (ff8) { vram_init(); + if (ff8_fix_uv_coords_precision) { + uv_patch_init(); + } vibration_init(); } @@ -1764,17 +1768,17 @@ struct texture_set *common_load_texture(struct texture_set *_texture_set, struct if(tex_format->palettes == 0) tex_format->palettes = VREF(tex_header, palette_entries); // convert texture data from source format and load it - if(texture_format != 0 && VREF(tex_header, image_data) != 0) + if(texture_format != 0 && VREF(tex_header, image_data) != 0 && (! ff8 || ! texturePacker.drawTexturesBackgroundIsDisabled())) { // detect changes in palette data for FF8, we can't trust it to notify us - if(ff8 && VREF(tex_header, palettes) > 0 && VREF(tex_header, version) != FB_TEX_VERSION) + if(ff8 && VREF(tex_header, palettes) > 0 && VREF(tex_header, version) != FB_TEX_VERSION && tex_format->bytesperpixel == 1) { if(!VREF(tex_header, old_palette_data)) { VRASS(tex_header, old_palette_data, (unsigned char*)external_malloc(4 * tex_format->palette_size)); } - if(memcmp(VREF(tex_header, old_palette_data), tex_format->palette_data, 4 * tex_format->palette_size)) + if(memcmp(VREF(tex_header, old_palette_data), tex_format->palette_data, 4 * tex_format->palette_size) != 0) { for (uint32_t idx = 0; idx < VREF(texture_set, ogl.gl_set->textures); idx++) newRenderer.deleteTexture(VREF(texture_set, texturehandle[idx])); diff --git a/src/ff8.h b/src/ff8.h index 6ffa2f1d..2488263a 100644 --- a/src/ff8.h +++ b/src/ff8.h @@ -1091,6 +1091,14 @@ struct ff8_externals uint32_t ssigpu_init; uint32_t *d3dcaps; uint32_t sub_53BB90; + uint32_t worldmap_fog_filter_polygons_in_block_1; + uint32_t worldmap_polygon_condition_2045C8C; + uint32_t worldmap_has_polygon_condition_2045C90; + uint32_t worldmap_sub_45DF20; + uint32_t sub_45E3A0; + uint32_t worldmap_fog_filter_polygons_in_block_2; + uint32_t sub_461E00; + uint32_t dword_1CA8848; uint32_t sub_53E2A0; uint32_t sub_53E6B0; uint32_t sub_4023D0; @@ -1102,7 +1110,7 @@ struct ff8_externals uint32_t sub_54A0D0; uint32_t sub_54D7E0; uint32_t sub_54FDA0; - uint32_t sub_53FAC0; + uint32_t worldmap_with_fog_sub_53FAC0; uint32_t sub_550070; int (*sub_541C80)(int); uint32_t sub_54B460; @@ -1120,7 +1128,6 @@ struct ff8_externals uint32_t stop_music; uint32_t set_midi_volume; uint32_t sub_46C050; - uint32_t sub_500900; uint32_t sub_501B60; uint32_t pause_music_and_sfx; uint32_t restart_music_and_sfx; @@ -1171,8 +1178,9 @@ struct ff8_externals uint32_t *ssigpu_callbacks_2; uint32_t sub_462AD0; uint32_t sub_462DF0; + uint32_t sub_461220; uint32_t ssigpu_tx_select_2_sub_465CE0; - int (*sub_464F70)(int, int, int, int, int, int, int, int, int, uint8_t *); + int (*sub_464F70)(struc_50 *, texture_page *, int, int, int, int, int, int, int, uint8_t *); void(*read_vram_1)(uint8_t *, int, uint8_t *, int, signed int, int, int); void(*read_vram_2_paletted)(uint8_t *, int, uint8_t *, int, signed int, int, int, uint16_t *); void(*read_vram_3_paletted)(uint8_t *, uint8_t *, signed int, int, int, uint16_t *); @@ -1247,6 +1255,19 @@ struct ff8_externals ff8_audio_fmt **sfx_audio_fmt; uint32_t manage_time_engine_sub_569971; int (*enable_rdtsc_sub_40AA00)(int enable); + uint32_t loc_47D490; + uint32_t sub_500870; + uint32_t sub_500C00; + uint32_t sub_500CC0; + uint32_t sub_506C90; + uint32_t sub_506CF0; + uint32_t sub_5084B0; + uint32_t battle_open_file_wrapper; + uint32_t battle_open_file; + char **battle_filenames; + uint32_t battle_load_textures_sub_500900; + uint32_t loc_5005A0; + uint32_t battle_upload_texture_to_vram; }; void ff8gl_field_78(struct ff8_polygon_set *polygon_set, struct ff8_game_obj *game_object); diff --git a/src/ff8/battle/stage.cpp b/src/ff8/battle/stage.cpp new file mode 100644 index 00000000..d205e6d8 --- /dev/null +++ b/src/ff8/battle/stage.cpp @@ -0,0 +1,265 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Cosmos // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx 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 // +// // +// FFNx 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. // +/****************************************************************************/ + +#include "stage.h" +#include "../../image/tim.h" +#include "../../saveload.h" +#include "../../log.h" + +#include +#include + +const uint8_t *ff8_battle_stage_search_model(const uint8_t *stage_data, size_t stage_data_size, std::list &model_offsets) +{ + const uint32_t *stage_data_32 = reinterpret_cast(stage_data); + const uint32_t *stage_data_end = reinterpret_cast(stage_data + stage_data_size); + bool ok = false; + + // Looking for 00010001 + while (stage_data_32 < stage_data_end) { + if (*stage_data_32 == 0x00010001) { + const uint32_t *stage_data_32_2 = stage_data_32 - 1; + + // Parse model offsets list + do { + model_offsets.push_front(*stage_data_32_2); + + if (*stage_data_32_2 == (model_offsets.size() + 1) * 4 && *(stage_data_32_2 - 1) == model_offsets.size()) { + ok = true; + break; + } + + --stage_data_32_2; + } while (*stage_data_32_2 != 0 && *stage_data_32_2 >= *(stage_data_32_2 - 1) && *stage_data_32_2 < stage_data_size); + + if (ok) { + break; + } + } + + ++stage_data_32; + } + + if (!ok) { + ffnx_warning("%s: models not found\n", __func__); + + return nullptr; + } + + return reinterpret_cast(stage_data_32 - model_offsets.size() - 1); +} + +bool ff8_battle_stage_parse_geometry(const uint8_t *stage_data, size_t stage_data_size, Stage &stage) +{ + std::list model_offsets; + + const uint8_t *models_section_start = ff8_battle_stage_search_model(stage_data + 0x500, stage_data_size - 0x500, model_offsets); + + if (models_section_start == nullptr) { + return false; + } + + do { + const uint8_t *after_vertices = nullptr; + + for (uint32_t offset: model_offsets) { + const uint8_t *model_section_start = models_section_start + offset; + uint16_t vertice_count = *(uint16_t *)(model_section_start + 4); + after_vertices = model_section_start + 6 + vertice_count * 6 + 4; + uint32_t unknown1 = *(uint32_t *)(after_vertices - 4); + + // Padding + after_vertices += (after_vertices - stage_data) % 4; + uint16_t triangles_count = *(uint16_t *)after_vertices, quads_count = *(((uint16_t *)after_vertices) + 1); + uint32_t unknown2 = *(uint32_t *)(after_vertices + 4); + + after_vertices += 8; + + for (int triangle_id = 0; triangle_id < triangles_count; ++triangle_id) { + FF8StageTriangle triangle = FF8StageTriangle(); + + memcpy(&triangle, after_vertices, sizeof(FF8StageTriangle)); + + stage.triangles.push_back(triangle); + + after_vertices += sizeof(FF8StageTriangle); + } + + for (int quad_id = 0; quad_id < quads_count; ++quad_id) { + FF8StageQuad quad = FF8StageQuad(); + + memcpy(&quad, after_vertices, sizeof(FF8StageQuad)); + + stage.quads.push_back(quad); + + after_vertices += sizeof(FF8StageQuad); + } + } + + if (after_vertices == nullptr) { + break; + } + + models_section_start = nullptr; + + uint32_t next_models_count = *(uint32_t *)(after_vertices + 0x98); + + if (next_models_count < 65535) { + const uint8_t *pos_next_model = after_vertices + 0x9C + next_models_count * sizeof(uint32_t); + + if (uint32_t(pos_next_model) > uint32_t(after_vertices) && pos_next_model - stage_data < stage_data_size && *(uint32_t *)pos_next_model == 0x00010001) + { + model_offsets.clear(); + models_section_start = ff8_battle_stage_search_model(pos_next_model, stage_data_size - (pos_next_model - stage_data), model_offsets); + } + } + } while (models_section_start != nullptr); + + return true; +} + +bool ff8_battle_state_save_texture(const Stage &stage, const Tim &tim, const char *filename) +{ + std::unordered_map > palsPerTexture; + + // Group palettes by texture ids + for (const FF8StageTriangle &triangle: stage.triangles) { + palsPerTexture[triangle.tex_id & 0xF].insert((triangle.pal_id >> 6) & 0xF); + } + + for (const FF8StageQuad &quad: stage.quads) { + palsPerTexture[quad.tex_id & 0xF].insert((quad.pal_id >> 6) & 0xF); + } + + std::vector texturesWithMultiPalette, texturesWithOnePalette; + std::vector rectangles; + + for (const std::pair > &pair: palsPerTexture) { + uint8_t texId = pair.first; + + if (pair.second.size() > 1) { + // List areas with palette ids + std::set rects; + + for (const FF8StageTriangle &triangle: stage.triangles) { + if (texId != (triangle.tex_id & 0xF)) { + continue; + } + + uint32_t x1, y1, x2, y2; + + x1 = std::min(std::min(triangle.u1, triangle.u2), triangle.u3); + y1 = std::min(std::min(triangle.v1, triangle.v2), triangle.v3); + x2 = std::max(std::max(triangle.u1, triangle.u2), triangle.u3); + y2 = std::max(std::max(triangle.v1, triangle.v2), triangle.v3); + + uint8_t palId = (triangle.pal_id >> 6) & 0xF; + TimRect rect(palId, x1, y1, x2, y2); + if (rect.isValid()) { + rects.insert(rect); + } + } + + for (const FF8StageQuad &quad: stage.quads) { + if (texId != (quad.tex_id & 0xF)) { + continue; + } + + uint32_t x1, y1, x2, y2; + + x1 = std::min(std::min(std::min(quad.u1, quad.u2), quad.u3), quad.u4); + y1 = std::min(std::min(std::min(quad.v1, quad.v2), quad.v3), quad.v4); + x2 = std::max(std::max(std::max(quad.u1, quad.u2), quad.u3), quad.u4); + y2 = std::max(std::max(std::max(quad.v1, quad.v2), quad.v3), quad.v4); + + uint8_t palId = (quad.pal_id >> 6) & 0xF; + TimRect rect(palId, x1, y1, x2, y2); + if (rect.isValid()) { + rects.insert(rect); + } + } + + // Merge areas together + std::set rectsMerged; + TimRect previousRect; + + while (true) { + for (const TimRect &rect: rects) { + if (! previousRect.isValid()) { + previousRect = rect; + + continue; + } + + if (previousRect.palIndex == rect.palIndex + && previousRect.x2 == rect.x1 + && previousRect.y1 == rect.y1 && previousRect.y2 == rect.y2) { // Same height, vertically aligned + previousRect.x2 = rect.x2; // Increase rect horizontally + } else if (previousRect.palIndex == rect.palIndex + && previousRect.x1 == rect.x1 && previousRect.x2 == rect.x2 + && previousRect.y2 == rect.y1) { // Same width, horizontally aligned + previousRect.y2 = rect.y2; // Increase rect vertically + } else if (previousRect.palIndex == rect.palIndex + && previousRect.x1 <= rect.x1 && previousRect.x2 >= rect.x2 + && previousRect.y1 <= rect.y1 && previousRect.y2 >= rect.y2) { + // Rect inside another + } else if (previousRect.palIndex == rect.palIndex + && previousRect.x1 >= rect.x1 && previousRect.x2 <= rect.x2 + && previousRect.y1 >= rect.y1 && previousRect.y2 <= rect.y2) { // Rect inside another + previousRect = rect; // Replace by bigger rect + } else { + // Flush + rectsMerged.insert(previousRect); + previousRect = rect; + } + } + + if (previousRect.isValid()) { + rectsMerged.insert(previousRect); + } + + if (rects.size() == rectsMerged.size()) { + break; + } + + rects = rectsMerged; + rectsMerged.clear(); + } + + for (const TimRect &rect: rectsMerged) { + rectangles.push_back(TimRect(rect.palIndex, texId * 128 + rect.x1, rect.y1, texId * 128 + rect.x2, rect.y2)); + } + + texturesWithMultiPalette.push_back(texId); + } else { + texturesWithOnePalette.push_back(texId); + } + + // Use first palette by default + rectangles.push_back(TimRect(*(pair.second.begin()), texId * 128, 0, texId * 128 + 255, 255)); + } + + tim.saveMultiPaletteTrianglesAndQuads(filename, rectangles, true); + + return true; +} diff --git a/src/ff8/battle/stage.h b/src/ff8/battle/stage.h new file mode 100644 index 00000000..68f96e40 --- /dev/null +++ b/src/ff8/battle/stage.h @@ -0,0 +1,62 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Cosmos // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx 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 // +// // +// FFNx 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. // +/****************************************************************************/ + +#pragma once + +#include "../../common.h" +#include "../../image/tim.h" + +#include + +struct FF8StageTriangle { + uint16_t faceA, faceB, faceC; + uint8_t u1, v1; + uint8_t u2, v2; + uint16_t pal_id; // 6 bits = Always 15 | 4 bits = PaletteID | 6 bits = Always 0 + uint8_t u3, v3; + uint8_t tex_id; + uint8_t hide; + uint8_t red, green, blue; + uint8_t instruction; +}; + +struct FF8StageQuad { + uint16_t faceA, faceB, faceC, faceD; + uint8_t u1, v1; + uint16_t pal_id; // 6 bits = Always 15 | 4 bits = PaletteID | 6 bits = Always 0 + uint8_t u2, v2; + uint8_t tex_id; + uint8_t hide; + uint8_t u3, v3; + uint8_t u4, v4; + uint8_t red, green, blue; + uint8_t instruction; +}; + +struct Stage { + std::vector triangles; + std::vector quads; +}; + +bool ff8_battle_stage_parse_geometry(const uint8_t *stage_data, size_t stage_data_size, Stage &stage); +bool ff8_battle_state_save_texture(const Stage &stage, const Tim &tim, const char *filename); diff --git a/src/ff8/texture_packer.cpp b/src/ff8/texture_packer.cpp index 24b9e2e2..89e41579 100644 --- a/src/ff8/texture_packer.cpp +++ b/src/ff8/texture_packer.cpp @@ -83,6 +83,8 @@ void TexturePacker::cleanTextures(ModdedTextureId previousTextureId, bool keepMo void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y, int wBpp2, int h, bool keepMods) { + if (trace_all || trace_vram) ffnx_trace("%s: textureId=%d\n", __func__, textureId); + for (int i = 0; i < h; ++i) { int vramY = y + i; @@ -127,19 +129,24 @@ void TexturePacker::setTexture(const char *name, int xBpp2, int y, int wBpp2, in if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s xBpp2=%d y=%d wBpp2=%d h=%d bpp=%d isPal=%d\n", __func__, hasNamedTexture ? name : "N/A", xBpp2, y, wBpp2, h, bpp, isPal); ModdedTextureId textureId = INVALID_TEXTURE; + Texture tex; if (hasNamedTexture && !isPal) { - Texture tex(name, xBpp2, y, wBpp2, h, bpp); + tex = Texture(name, xBpp2, y, wBpp2, h, bpp); if (tex.createImage()) { textureId = makeTextureId(xBpp2, y, TextureCategoryStandard); - _externalTextures[textureId] = tex; } } setVramTextureId(textureId, xBpp2, y, wBpp2, h); + + if (textureId != INVALID_TEXTURE) + { + _externalTextures[textureId] = tex; + } } bool TexturePacker::setTextureBackground(const char *name, int x, int y, int w, int h, const std::vector &mapTiles, int bgTexId) @@ -148,18 +155,22 @@ bool TexturePacker::setTextureBackground(const char *name, int x, int y, int w, TextureBackground tex(name, x, y, w, h, mapTiles, bgTexId); ModdedTextureId textureId = INVALID_TEXTURE; - bool imageFound = false; if (tex.createImage()) { textureId = makeTextureId(x, y, TextureCategoryBackground); - _backgroundTextures[textureId] = tex; - imageFound = true; } setVramTextureId(textureId, x, y, w, h); - return imageFound; + if (textureId != INVALID_TEXTURE) + { + _backgroundTextures[textureId] = tex; + + return true; + } + + return false; } bool TexturePacker::setTextureRedirection(const TextureInfos &oldTexture, const TextureInfos &newTexture, uint32_t *imageData) @@ -172,10 +183,11 @@ bool TexturePacker::setTextureRedirection(const TextureInfos &oldTexture, const if (redirection.isValid() && redirection.createImage(imageData)) { ModdedTextureId textureId = makeTextureId(oldTexture.x(), oldTexture.y(), TextureCategoryRedirection); - _textureRedirections[textureId] = redirection; setVramTextureId(textureId, oldTexture.x(), oldTexture.y(), oldTexture.w(), oldTexture.h(), true); + _textureRedirections[textureId] = redirection; + return true; } @@ -317,11 +329,6 @@ TexturePacker::TextureTypes TexturePacker::drawTextures(const uint8_t *texData, if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s tex=(%d, %d) bpp=%d paletteIndex=%d\n", __func__, tex.x, tex.y, tex.bpp, paletteIndex); - if (getmode_cached()->driver_mode == MODE_BATTLE) - { - return NoTexture; - } - return drawTextures(target, tex, originalW, originalH, scale, paletteIndex); } @@ -350,7 +357,11 @@ TexturePacker::TextureTypes TexturePacker::drawTextures(uint32_t *target, const return NoTexture; } - if (_disableDrawTexturesBackground && (trace_all || trace_vram)) ffnx_info("TexturePacker::%s disabled\n", __func__); + if (_disableDrawTexturesBackground && (trace_all || trace_vram)) ffnx_info("TexturePacker::%s disabled for field background\n", __func__); + + if (_disableDrawTexturesBackground) { + return NoTexture; + } TextureTypes drawnTextureTypes = NoTexture; long double totalCopyRect = 0.0, totalCopyFind = 0.0; @@ -442,23 +453,25 @@ TexturePacker::TextureTypes TexturePacker::drawTextures(uint32_t *target, const void TexturePacker::registerTiledTex(const uint8_t *texData, int x, int y, Tim::Bpp bpp, int palX, int palY) { - if (trace_all || trace_vram) ffnx_trace("%s pointer=0x%X x=%d y=%d bpp=%d palX=%d palY=%d psx_texture_pages=%X\n", __func__, texData, x, y, bpp, palX, palY, ff8_externals.psx_texture_pages); + if (trace_all || trace_vram) ffnx_trace("%s pointer=0x%X x=%d y=%d bpp=%d palX=%d palY=%d\n", __func__, texData, x, y, bpp, palX, palY); _tiledTexs[texData] = TiledTex(x, y, bpp, palX, palY); } -void TexturePacker::debugSaveTexture(int textureId, const uint32_t *source, int w, int h, bool after) +void TexturePacker::debugSaveTexture(int textureId, const uint32_t *source, int w, int h, bool removeAlpha, bool after) { uint32_t *target = new uint32_t[w * h]; for (int i = 0; i < h * w; ++i) { - target[i] = source[i] | (0xffu << 24); // Remove alpha + target[i] = removeAlpha ? source[i] | 0xff000000 : source[i]; // Remove alpha } char filename[MAX_PATH]; snprintf(filename, sizeof(filename), "texture-%d-%s", textureId, after ? "z-after" : "a-before"); + if (trace_all || trace_vram) ffnx_trace("%s %s\n", __func__, filename); + save_texture(target, w * h * 4, w, h, -1, filename, false); delete[] target; @@ -670,10 +683,23 @@ TexturePacker::TextureBackground::TextureBackground( int textureId ) : Texture(name, x, y, w, h, Tim::Bpp16), _mapTiles(mapTiles), _textureId(textureId) { +} + +bool TexturePacker::TextureBackground::createImage() +{ + size_t size = _mapTiles.size(); + + _colsCount = size / (TEXTURE_HEIGHT / TILE_SIZE) + int(size % (TEXTURE_HEIGHT / TILE_SIZE) != 0); + + if (! Texture::createImage(0, false)) { + return false; + } + // Build tileIdsByPosition for fast lookup - _tileIdsByPosition.reserve(mapTiles.size()); + _tileIdsByPosition.reserve(size); + size_t tileId = 0; - for (const Tile &tile: mapTiles) { + for (const Tile &tile: _mapTiles) { const uint8_t texId = tile.texID & 0xF; if (_textureId < 0 || _textureId == texId) { const uint8_t bpp = (tile.texID >> 7) & 3; @@ -681,13 +707,14 @@ TexturePacker::TextureBackground::TextureBackground( auto it = _tileIdsByPosition.find(key); // Remove some duplicates (but keep those which render differently) - if (it == _tileIdsByPosition.end() || ! ff8_background_tiles_looks_alike(tile, mapTiles.at(it->second))) { + if (it == _tileIdsByPosition.end() || ! ff8_background_tiles_looks_alike(tile, _mapTiles.at(it->second))) { _tileIdsByPosition.insert(std::pair(key, tileId)); } } ++tileId; } - _colsCount = mapTiles.size() / (TEXTURE_HEIGHT / TILE_SIZE) + int(mapTiles.size() % (TEXTURE_HEIGHT / TILE_SIZE) != 0); + + return true; } void TexturePacker::TextureBackground::copyRect(int vramXBpp2, int vramY, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const @@ -801,7 +828,7 @@ void TexturePacker::TextureRedirection::copyRect(int vramXBpp2, int vramY, Tim:: { if (textureBpp == Tim::Bpp4) { TextureInfos::copyRect( - _image, vramXBpp2 - oldTexture().x(), vramY - oldTexture().y(), pixelW(), _scale, _oldTexture.bpp(), + _image, vramXBpp2 - _oldTexture.x(), vramY - _oldTexture.y(), pixelW(), _scale, _oldTexture.bpp(), target, targetX, targetY, targetW, targetScale ); } diff --git a/src/ff8/texture_packer.h b/src/ff8/texture_packer.h index e9428dcf..10f24ae3 100644 --- a/src/ff8/texture_packer.h +++ b/src/ff8/texture_packer.h @@ -100,10 +100,13 @@ class TexturePacker { void registerTiledTex(const uint8_t *texData, int x, int y, Tim::Bpp bpp, int palX = 0, int palY = 0); void disableDrawTexturesBackground(bool disabled); + inline bool drawTexturesBackgroundIsDisabled() const { + return _disableDrawTexturesBackground; + } TextureTypes drawTextures(const uint8_t *texData, struct texture_format *tex_format, uint32_t *target, const uint32_t *originalImageData, int originalW, int originalH, uint8_t scale, uint32_t paletteIndex); bool saveVram(const char *fileName, Tim::Bpp bpp) const; - static void debugSaveTexture(int textureId, const uint32_t *source, int w, int h, bool after); + static void debugSaveTexture(int textureId, const uint32_t *source, int w, int h, bool removeAlpha, bool after); private: enum TextureCategory { TextureCategoryStandard, @@ -164,9 +167,7 @@ class TexturePacker { const std::vector &mapTiles, int textureId ); - bool createImage() { - return Texture::createImage(0, false); - } + bool createImage(); void copyRect(int sourceXBpp2, int sourceYBpp2, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const; private: virtual uint8_t computeScale() const override; diff --git a/src/ff8/uv_patch.cpp b/src/ff8/uv_patch.cpp new file mode 100644 index 00000000..498246df --- /dev/null +++ b/src/ff8/uv_patch.cpp @@ -0,0 +1,173 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx 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 // +// // +// FFNx 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. // +/****************************************************************************/ + +#include "uv_patch.h" + +#include "../log.h" +#include "../patch.h" + +struct TexCoord { + uint8_t x, y; +}; + +struct MapBlockPolygon +{ + uint8_t vi[3]; + uint8_t ni[3]; + TexCoord pos[3]; + uint8_t texi, groundType; + uint16_t flags; +}; + +struct SsigpuExecutionInstruction { + uint32_t field_0; + uint8_t r, g, b, func_id; + uint32_t vertex_a; + TexCoord tex_coord_a; + uint16_t tex_pos_x6_y9; + uint32_t field_10; + uint32_t vertex_b; + TexCoord tex_coord_b; + uint16_t tex_header; + uint32_t field_1C; + uint32_t vertex_c; + TexCoord tex_coord_c; + uint16_t field_26; +}; + +int current_polygon = -1; +void *current_polygon_condition_old = nullptr; +bool old_dword_2045C90_value = false; +uint8_t *current_block_data_start = nullptr; +float *maybe_hundred_bak = nullptr; + +void worldmap_fog_filter_polygons_in_block_leave() +{ + *(bool *)ff8_externals.worldmap_has_polygon_condition_2045C90 = old_dword_2045C90_value; + *(void **)ff8_externals.worldmap_polygon_condition_2045C8C = current_polygon_condition_old; +} + +void worldmap_fog_filter_polygons_in_block_1(uint8_t *block, int a2, void *out) +{ + current_block_data_start = block; + + ((void(*)(uint8_t*,int,void*))ff8_externals.worldmap_fog_filter_polygons_in_block_1)(block, a2, out); + + worldmap_fog_filter_polygons_in_block_leave(); +} + +void worldmap_fog_filter_polygons_in_block_2(uint8_t *block, int a2, void *out) +{ + current_block_data_start = block; + + ((void(*)(uint8_t*,int,void*))ff8_externals.worldmap_fog_filter_polygons_in_block_2)(block, a2, out); + + worldmap_fog_filter_polygons_in_block_leave(); +} + +bool current_polygon_condition(int polygon_id) +{ + current_polygon = polygon_id; + + if (!old_dword_2045C90_value) { + return true; + } + + return ((bool(*)(int))current_polygon_condition_old)(polygon_id); +} + +void sub_45DF20(int a1, int a2, int a3) +{ + void **current_polygon_condition_dword_2045964 = (void **)ff8_externals.worldmap_polygon_condition_2045C8C; + + old_dword_2045C90_value = *(bool *)ff8_externals.worldmap_has_polygon_condition_2045C90; + + if (!old_dword_2045C90_value) { + *(bool *)ff8_externals.worldmap_has_polygon_condition_2045C90 = true; + current_polygon_condition_old = *current_polygon_condition_dword_2045964; + *current_polygon_condition_dword_2045964 = current_polygon_condition; + } + + ((void(*)(int,int,int))ff8_externals.worldmap_sub_45DF20)(a1, a2, a3); +} + +void enrich_tex_coords_sub_45E3A0(SsigpuExecutionInstruction *a1) +{ + MapBlockPolygon *polygon = (MapBlockPolygon *)(current_block_data_start + 4 + current_polygon * sizeof(MapBlockPolygon)); + // Save the last bit of texture coordinates in field_26 + a1->field_26 = ((polygon->pos[0].x & 1) << 0) | ((polygon->pos[0].y & 1) << 1) + | ((polygon->pos[1].x & 1) << 2) | ((polygon->pos[1].y & 1) << 3) + | ((polygon->pos[2].x & 1) << 4) | ((polygon->pos[2].y & 1) << 5); + + ((void(*)(SsigpuExecutionInstruction*))ff8_externals.sub_45E3A0)(a1); +} + +void ssigpu_callback_sub_461E00(SsigpuExecutionInstruction *a1) +{ + uint8_t lost_bits = a1->field_26; + + ((void(*)(SsigpuExecutionInstruction*))ff8_externals.sub_461E00)(a1); + + if (!(*(int *)ff8_externals.dword_1CA8848) && maybe_hundred_bak != nullptr) { + // Not 512 to avoid space between textures + maybe_hundred_bak[6] = double(uint32_t(a1->tex_coord_a.x) * 2 + ((lost_bits >> 0) & 1)) / 511.999; + maybe_hundred_bak[7] = double(uint32_t(a1->tex_coord_a.y) * 2 + ((lost_bits >> 1) & 1)) / 511.999; + maybe_hundred_bak[14] = double(uint32_t(a1->tex_coord_b.x) * 2 + ((lost_bits >> 2) & 1)) / 511.999; + maybe_hundred_bak[15] = double(uint32_t(a1->tex_coord_b.y) * 2 + ((lost_bits >> 3) & 1)) / 511.999; + maybe_hundred_bak[22] = double(uint32_t(a1->tex_coord_c.x) * 2 + ((lost_bits >> 4) & 1)) / 511.999; + maybe_hundred_bak[23] = double(uint32_t(a1->tex_coord_c.y) * 2 + ((lost_bits >> 5) & 1)) / 511.999; + + maybe_hundred_bak = nullptr; + } +} + +void ssigpu_sub_461220(float *maybe_hundred) +{ + maybe_hundred_bak = maybe_hundred; + + ((void(*)(float*))ff8_externals.sub_461220)(maybe_hundred); +} + +void uv_patch_init() +{ + /* We hook a lot of stuff here to bring back full precision in texture UVs + * - We need the UVs directly from the game data. To obtain this, + * we get the current block data and the current polygon from the game. + * - Then we put this information into the SSIGPU instruction, the game will + * only stores the UVs divided by 2, so we need to remember the forgotten bits at least. + * - And when the game uses the UVs from the SSIGPU instruction, we alter + * the computation of U and V using the forgotten bits. + */ + bool isUs = version == VERSION_FF8_12_US || version == VERSION_FF8_12_US_NV || version == VERSION_FF8_12_US_EIDOS || version == VERSION_FF8_12_US_EIDOS_NV; + + // Worldmap with Fog enabled + replace_call(ff8_externals.sub_53BB90 + (isUs ? 0x42D : 0x43B), worldmap_fog_filter_polygons_in_block_1); + replace_call(ff8_externals.sub_53BB90 + (isUs ? 0xADD : 0xB11), worldmap_fog_filter_polygons_in_block_1); + replace_call(ff8_externals.sub_53BB90 + (isUs ? 0x442 : 0x450), worldmap_fog_filter_polygons_in_block_2); + replace_call(ff8_externals.sub_53BB90 + (isUs ? 0xAFE : 0xB26), worldmap_fog_filter_polygons_in_block_2); + replace_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1 + (isUs ? 0x239 : 0x202), sub_45DF20); + replace_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1 + (isUs ? 0x5C8 : 0x5DC), enrich_tex_coords_sub_45E3A0); + replace_call(ff8_externals.worldmap_fog_filter_polygons_in_block_2 + (isUs ? 0x281 : 0x241), sub_45DF20); + replace_call(ff8_externals.worldmap_fog_filter_polygons_in_block_2 + (isUs ? 0x698 : 0x6CD), enrich_tex_coords_sub_45E3A0); + replace_call(ff8_externals.sub_461E00 + 0x50, ssigpu_sub_461220); + ff8_externals.ssigpu_callbacks_1[52] = uint32_t(ssigpu_callback_sub_461E00); +} diff --git a/src/ff8/uv_patch.h b/src/ff8/uv_patch.h new file mode 100644 index 00000000..9edbb2ca --- /dev/null +++ b/src/ff8/uv_patch.h @@ -0,0 +1,25 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx 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 // +// // +// FFNx 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. // +/****************************************************************************/ + +#pragma once + +void uv_patch_init(); diff --git a/src/ff8/vram.cpp b/src/ff8/vram.cpp index db8957a1..6e01b10c 100644 --- a/src/ff8/vram.cpp +++ b/src/ff8/vram.cpp @@ -23,9 +23,12 @@ #include "../ff8.h" #include "../patch.h" +#include "../macro.h" #include "../image/tim.h" #include "field/background.h" #include "field/chara_one.h" +#include "battle/stage.h" +#include "file.h" #include #include @@ -52,6 +55,9 @@ int chara_one_current_pos = 0; uint32_t chara_one_current_model = 0; uint32_t chara_one_current_mch = 0; uint32_t chara_one_current_texture = 0; +// Battle +char battle_texture_name[MAX_PATH] = ""; +int battle_texture_id = 0; void ff8_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) { @@ -72,14 +78,14 @@ void ff8_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) *next_texture_name = '\0'; } -int read_vram_to_buffer_parent_call1(int a1, int structure, int x, int y, int w, int h, int bpp, int rel_pos, int a9, uint8_t *target) +int read_vram_to_buffer_parent_call1(struc_50 *psxvram, texture_page *tex_page, int x, int y, int w, int h, int bpp, int rel_pos, int a9, uint8_t *target) { if (trace_all || trace_vram) ffnx_trace("%s: x=%d y=%d w=%d h=%d bpp=%d rel_pos=(%d, %d) a9=%d target=%X\n", __func__, x, y, w, h, bpp, rel_pos & 0xF, rel_pos >> 4, a9, target); next_psxvram_x = (x >> (2 - bpp)) + ((rel_pos & 0xF) << 6); next_psxvram_y = y + (((rel_pos >> 4) & 1) << 8); - int ret = ff8_externals.sub_464F70(a1, structure, x, y, w, h, bpp, rel_pos, a9, target); + int ret = ff8_externals.sub_464F70(psxvram, tex_page, x, y, w, h, bpp, rel_pos, a9, target); next_psxvram_x = -1; next_psxvram_y = -1; @@ -339,14 +345,10 @@ int ff8_wm_open_data(const char *path, int32_t pos, uint32_t size, void *data) void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) { - snprintf(next_texture_name, MAX_PATH, "world/dat/texl/texture%d", next_texl_id); - Tim tim = Tim::fromTimData(texture_buffer - 20); if (trace_all || trace_vram) ffnx_trace("%s texl_id=%d pos=(%d, %d) palPos=(%d, %d)\n", __func__, next_texl_id, tim.imageX(), tim.imageY(), tim.paletteX(), tim.paletteY()); - if (save_textures) tim.saveMultiPaletteGrid(next_texture_name, 4, 4, 0, 4, true); - next_bpp = Tim::Bpp8; ff8_upload_vram(pos_and_size, texture_buffer); @@ -358,7 +360,6 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf // Worldmap texture fix - bool is_left = pos_and_size[1] == 224; uint16_t oldX = 16 * (next_texl_id - 2 * ((next_texl_id / 2) & 1) + (next_texl_id & 1)), oldY = ((next_texl_id / 2) & 1) ? 384 : 256; if (next_texl_id == 18 || next_texl_id == 19) @@ -406,6 +407,11 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf return; } + for (int i = 0; i < newTexture.pixelW() * newTexture.h(); ++i) + { + image[i] = (image[i] & 0xFFFFFF) | ((image[i] & 0xFF000000) == 0 ? 0 : 0x7F000000); // Force alpha + } + if (! texturePacker.setTextureRedirection(oldTexture, newTexture, image)) { if (trace_all || trace_vram) ffnx_warning("%s: invalid redirection\n"); @@ -413,11 +419,18 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf } } - // Reload texture TODO: reload only relevant parts - ff8_externals.psx_texture_pages[0].struc_50_array[16].vram_needs_reload = 0xFF; - ff8_externals.psx_texture_pages[0].struc_50_array[17].vram_needs_reload = 0xFF; - ff8_externals.psx_texture_pages[0].struc_50_array[18].vram_needs_reload = 0xFF; - ff8_externals.psx_texture_pages[0].struc_50_array[19].vram_needs_reload = 0xFF; + int section = 16 + oldX / 64; + // Reload texture + struc_50 *stru50 = ff8_externals.psx_texture_pages[0].struc_50_array + section; + + for (int page = 0; page < 8; ++page) { + if ((stru50->initialized >> page) & 1) { + ff8_texture_set *texture_set = (struct ff8_texture_set *)stru50->texture_page[page].tri_gfxobj->hundred_data->texture_set; + + common_unload_texture((struct texture_set *)texture_set); + common_load_texture((struct texture_set *)texture_set, texture_set->tex_header, texture_set->texture_format); + } + } } void ff8_field_mim_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) @@ -435,14 +448,16 @@ uint32_t ff8_field_read_map_data(char *filename, uint8_t *map_data) uint32_t ret = ff8_externals.sm_pc_read(filename, map_data); + std::vector tiles = ff8_background_parse_tiles(map_data); + char tex_filename[MAX_PATH] = {}; snprintf(tex_filename, MAX_PATH, "field/mapdata/%s/%s", get_current_field_name(), get_current_field_name()); - std::vector tiles = ff8_background_parse_tiles(map_data); - if (save_textures) { ff8_background_save_textures(tiles, mim_texture_buffer, tex_filename); + + return ret; } if (!texturePacker.setTextureBackground(tex_filename, 0, 256, VRAM_PAGE_MIM_MAX_COUNT * TEXTURE_WIDTH_BPP16, TEXTURE_HEIGHT, tiles)) { @@ -575,18 +590,48 @@ void ff8_field_effects_upload_vram1(int16_t *pos_and_size, uint8_t *texture_buff texturePacker.setTexture(texture_name, pos_and_size[0], pos_and_size[1], pos_and_size[2], pos_and_size[3] * 2, bpp, false); } -DWORD *create_graphics_object_load_texture_call2(int a1, int a2, char *path, void *data, ff8_game_obj *game_object) +Stage stage; + +int16_t ff8_battle_open_and_read_file(int fileId, void *data, int a3, int callback) +{ + snprintf(battle_texture_name, sizeof(battle_texture_name), "battle/%s", ff8_externals.battle_filenames[fileId]); + + return ((int16_t(*)(int,void*,int,int))ff8_externals.battle_open_file)(fileId, data, a3, callback); +} + +size_t ff8_battle_read_file(char *fileName, void *data) { - if (trace_all || trace_vram) ffnx_trace("%s path=%s\n", __func__, path == nullptr ? "(none)" : path); + battle_texture_id = 0; - // Optimization: when the game creates 3D objects for field background, it does upload textures, but it does not display it - texturePacker.disableDrawTexturesBackground(true); + size_t file_size = ff8_externals.sm_pc_read(fileName, data); - DWORD *ret = ((DWORD*(*)(int,int,char*,void*,ff8_game_obj*))ff8_externals.sub_4076B6)(a1, a2, path, data, game_object); + if (save_textures && StrStrIA(fileName, ".X") != nullptr) { + ff8_battle_stage_parse_geometry((uint8_t *)data, file_size, stage); + } - texturePacker.disableDrawTexturesBackground(false); + return file_size; +} - return ret; +void ff8_battle_upload_texture_palette(int16_t *pos_and_size, uint8_t *texture_buffer) +{ + if (trace_all || trace_vram) ffnx_trace("%s: %s\n", __func__, battle_texture_name); + + Tim tim = Tim::fromTimData(texture_buffer - 20); + + ff8_upload_vram(pos_and_size, texture_buffer); + + next_bpp = tim.bpp(); + snprintf(next_texture_name, sizeof(next_texture_name), "%s-%d", battle_texture_name, battle_texture_id); + + ++battle_texture_id; + + if (save_textures) { + if (StrStrIA(battle_texture_name, ".X") != nullptr) { + ff8_battle_state_save_texture(stage, tim, next_texture_name); + } else { + tim.save(next_texture_name, 0, 0, false); + } + } } void vram_init() @@ -620,6 +665,11 @@ void vram_init() replace_call(ff8_externals.load_field_models + 0xB72, ff8_field_texture_upload_one); // field: effects replace_call(ff8_externals.upload_pmp_file + 0x7F, ff8_field_effects_upload_vram1); + // battle + replace_call(ff8_externals.battle_open_file_wrapper + 0x14, ff8_battle_open_and_read_file); + replace_call(ff8_externals.battle_open_file + 0x8E, ff8_battle_read_file); + replace_call(ff8_externals.battle_open_file + 0x1A2, ff8_battle_read_file); + replace_call(ff8_externals.battle_upload_texture_to_vram + 0x45, ff8_battle_upload_texture_palette); replace_function(ff8_externals.upload_psx_vram, ff8_upload_vram); @@ -644,8 +694,6 @@ void vram_init() replace_call(ff8_externals.sub_464DB0 + 0xEC, read_vram_to_buffer_with_palette1); replace_call(ff8_externals.sub_465720 + 0xA5, read_vram_to_buffer_with_palette1); - replace_call(ff8_externals._load_texture + 0x24E, create_graphics_object_load_texture_call2); - // Not used? replace_call(ff8_externals.sub_4649A0 + 0x13F, read_vram_to_buffer_with_palette2); } diff --git a/src/ff8_data.cpp b/src/ff8_data.cpp index 7a609846..82d021a8 100644 --- a/src/ff8_data.cpp +++ b/src/ff8_data.cpp @@ -158,6 +158,21 @@ void ff8_find_externals() ff8_externals.sub_539500 = get_relative_call(ff8_externals.sub_534640, 0x110); ff8_externals.cardgame_tim_texture_font = (uint8_t *)get_absolute_value(ff8_externals.sub_539500, 0x1); + ff8_externals.loc_47D490 = ff8_externals.sub_47CCB0 + 0xDA + 0x4 + *((int32_t *)(ff8_externals.sub_47CCB0 + 0xDA)); + ff8_externals.sub_500870 = get_relative_call(ff8_externals.loc_47D490, 0x85); + ff8_externals.sub_500C00 = get_relative_call(ff8_externals.sub_500870, 0x31); + ff8_externals.sub_500CC0 = get_absolute_value(ff8_externals.sub_500C00, 0x9A); + ff8_externals.sub_506C90 = get_relative_call(ff8_externals.sub_500CC0, 0x7F); + ff8_externals.sub_506CF0 = get_absolute_value(ff8_externals.sub_506C90, 0x2F); + ff8_externals.sub_5084B0 = get_relative_call(ff8_externals.sub_506CF0, 0x2A); + ff8_externals.battle_open_file_wrapper = get_relative_call(ff8_externals.sub_5084B0, 0x1B); + ff8_externals.battle_open_file = get_relative_call(ff8_externals.battle_open_file_wrapper, 0x14); + ff8_externals.battle_filenames = (char **)get_absolute_value(ff8_externals.battle_open_file, 0x11); + + ff8_externals.battle_load_textures_sub_500900 = get_relative_call(ff8_externals.sub_47CCB0, 0x98D); + ff8_externals.loc_5005A0 = ff8_externals.battle_load_textures_sub_500900 + 0x9D + 0x4 + *((int32_t *)(ff8_externals.battle_load_textures_sub_500900 + 0x9D)); + ff8_externals.battle_upload_texture_to_vram = get_relative_call(ff8_externals.loc_5005A0, 0xD1); + ff8_externals.fonts = (font_object **)get_absolute_value(ff8_externals.load_fonts, JP_VERSION ? 0x17 : 0x16); common_externals.assert_malloc = (void* (*)(uint32_t, const char*, uint32_t))get_relative_call(ff8_externals.load_fonts, JP_VERSION ? 0x29 : 0x2A); @@ -292,10 +307,13 @@ void ff8_find_externals() ff8_externals.ssigpu_callbacks_1 = (uint32_t *)get_absolute_value(ff8_externals.sub_45D080, 0x21E); ff8_externals.ssigpu_callbacks_2 = (uint32_t *)get_absolute_value(ff8_externals.sub_45D080, 0x122); + ff8_externals.sub_461E00 = ff8_externals.ssigpu_callbacks_1[52]; + ff8_externals.sub_461220 = get_relative_call(ff8_externals.sub_461E00, 0x50); + ff8_externals.dword_1CA8848 = get_absolute_value(ff8_externals.sub_461E00, 0x56); ff8_externals.sub_462AD0 = get_relative_call(ff8_externals.ssigpu_callbacks_1[116], 0x13); ff8_externals.sub_462DF0 = get_relative_call(ff8_externals.sub_462AD0, 0x62); ff8_externals.ssigpu_tx_select_2_sub_465CE0 = get_relative_call(ff8_externals.sub_462DF0, 0x33); - ff8_externals.sub_464F70 = (int(*)(int,int,int,int,int,int,int,int,int,uint8_t*))get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x281); + ff8_externals.sub_464F70 = (int(*)(struc_50*,texture_page*,int,int,int,int,int,int,int,uint8_t*))get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x281); ff8_externals.read_vram_1 = (void(*)(uint8_t*,int,uint8_t*,int,signed int,int,int))get_relative_call(uint32_t(ff8_externals.sub_464F70), 0x2C5); ff8_externals.sub_464DB0 = get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x2CF); ff8_externals.read_vram_2_paletted = (void(*)(uint8_t*,int,uint8_t*,int,signed int,int,int,uint16_t*))get_relative_call(ff8_externals.sub_464DB0, 0xEC); @@ -413,8 +431,7 @@ void ff8_find_externals() ff8_externals.stop_cdrom_cleanup_call = ff8_externals.pubintro_init + 0x183; // Pause/Resume functions - ff8_externals.sub_500900 = get_relative_call(ff8_externals.sub_47CCB0, 0x98D); - ff8_externals.sub_501B60 = get_relative_call(ff8_externals.sub_500900, -0x2A2); + ff8_externals.sub_501B60 = get_relative_call(ff8_externals.battle_load_textures_sub_500900, -0x2A2); ff8_externals.pause_music_and_sfx = get_relative_call(ff8_externals.sub_501B60, 0x54); common_externals.pause_wav = get_relative_call(ff8_externals.pause_music_and_sfx, 0xF); common_externals.pause_midi = get_relative_call(ff8_externals.pause_music_and_sfx, 0x17); @@ -478,27 +495,33 @@ void ff8_find_externals() ff8_externals.show_vram_window = (void (*)())get_relative_call(ff8_externals.worldmap_main_loop, 0xA0); ff8_externals.refresh_vram_window = (void (*)())get_relative_call(ff8_externals.worldmap_main_loop, 0xA5); - ff8_externals.sub_53FAC0 = get_relative_call(ff8_externals.worldmap_main_loop, 0x134); - ff8_externals.sub_54B460 = get_relative_call(ff8_externals.sub_53FAC0, 0x5D7); + ff8_externals.worldmap_with_fog_sub_53FAC0 = get_relative_call(ff8_externals.worldmap_main_loop, 0x134); + ff8_externals.sub_54B460 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x5D7); - ff8_externals.sub_549E80 = get_relative_call(ff8_externals.sub_53FAC0, 0x1D5); - ff8_externals.sub_550070 = get_relative_call(ff8_externals.sub_53FAC0, 0x278); + ff8_externals.sub_549E80 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x1D5); + ff8_externals.sub_550070 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x278); ff8_externals.vibrate_data_world = (uint8_t *)get_absolute_value(ff8_externals.sub_550070, 0xA82); - ff8_externals.sub_53BB90 = get_relative_call(ff8_externals.sub_53FAC0, 0x2D4); + ff8_externals.sub_53BB90 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x2D4); ff8_externals.sub_53E2A0 = get_relative_call(ff8_externals.sub_53BB90, 0x327); ff8_externals.sub_53E6B0 = get_relative_call(ff8_externals.sub_53E2A0, 0x36B); ff8_externals.sub_4023D0 = get_relative_call(ff8_externals.sub_53BB90, 0xAB1); - ff8_externals.sub_53C750 = get_relative_call(ff8_externals.sub_53FAC0, 0x2DB); - ff8_externals.sub_54FDA0 = get_relative_call(ff8_externals.sub_53FAC0, 0x375); - ff8_externals.sub_54D7E0 = get_relative_call(ff8_externals.sub_53FAC0, 0x3C2); - ff8_externals.sub_544630 = get_relative_call(ff8_externals.sub_53FAC0, 0x3D2); - ff8_externals.sub_545EA0 = get_relative_call(ff8_externals.sub_53FAC0, 0x4BF); + ff8_externals.worldmap_fog_filter_polygons_in_block_1 = get_relative_call(ff8_externals.sub_53BB90, 0x42D); + ff8_externals.worldmap_has_polygon_condition_2045C90 = get_absolute_value(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x29); + ff8_externals.worldmap_polygon_condition_2045C8C = get_absolute_value(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x59); + ff8_externals.worldmap_sub_45DF20 = get_relative_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x1FC); + ff8_externals.sub_45E3A0 = get_relative_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x4A4); + ff8_externals.worldmap_fog_filter_polygons_in_block_2 = get_relative_call(ff8_externals.sub_53BB90, 0x442); + ff8_externals.sub_53C750 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x2DB); + ff8_externals.sub_54FDA0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x375); + ff8_externals.sub_54D7E0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x3C2); + ff8_externals.sub_544630 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x3D2); + ff8_externals.sub_545EA0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x4BF); ff8_externals.sub_545F10 = get_relative_call(ff8_externals.sub_545EA0, 0x20); ff8_externals.sub_546100 = get_relative_call(ff8_externals.sub_545F10, 0x58); - ff8_externals.battle_trigger_worldmap = ff8_externals.sub_53FAC0 + 0x4E6; + ff8_externals.battle_trigger_worldmap = ff8_externals.worldmap_with_fog_sub_53FAC0 + 0x4E6; } else { @@ -516,25 +539,31 @@ void ff8_find_externals() ff8_externals.show_vram_window = (void (*)())get_relative_call(ff8_externals.worldmap_main_loop, 0xA3); ff8_externals.refresh_vram_window = (void (*)())get_relative_call(ff8_externals.worldmap_main_loop, 0xA8); - ff8_externals.sub_53FAC0 = get_relative_call(ff8_externals.worldmap_main_loop, 0x137); - ff8_externals.sub_54B460 = get_relative_call(ff8_externals.sub_53FAC0, 0x5D9); + ff8_externals.worldmap_with_fog_sub_53FAC0 = get_relative_call(ff8_externals.worldmap_main_loop, 0x137); + ff8_externals.sub_54B460 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x5D9); - ff8_externals.sub_549E80 = get_relative_call(ff8_externals.sub_53FAC0, 0x1D6); - ff8_externals.sub_550070 = get_relative_call(ff8_externals.sub_53FAC0, 0x279); + ff8_externals.sub_549E80 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x1D6); + ff8_externals.sub_550070 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x279); ff8_externals.vibrate_data_world = (uint8_t *)get_absolute_value(ff8_externals.sub_550070, 0xAFA); - ff8_externals.sub_53BB90 = get_relative_call(ff8_externals.sub_53FAC0, 0x2D5); + ff8_externals.sub_53BB90 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x2D5); ff8_externals.sub_53E2A0 = get_relative_call(ff8_externals.sub_53BB90, 0x336); ff8_externals.sub_53E6B0 = get_relative_call(ff8_externals.sub_53E2A0, 0x39A); ff8_externals.sub_4023D0 = get_relative_call(ff8_externals.sub_53BB90, 0xAE5); + ff8_externals.worldmap_fog_filter_polygons_in_block_1 = get_relative_call(ff8_externals.sub_53BB90, 0x43B); + ff8_externals.worldmap_has_polygon_condition_2045C90 = get_absolute_value(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x51); + ff8_externals.worldmap_polygon_condition_2045C8C = get_absolute_value(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x5D); + ff8_externals.worldmap_sub_45DF20 = get_relative_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x202); + ff8_externals.sub_45E3A0 = get_relative_call(ff8_externals.worldmap_fog_filter_polygons_in_block_1, 0x4B8); + ff8_externals.worldmap_fog_filter_polygons_in_block_2 = get_relative_call(ff8_externals.sub_53BB90, 0x450); if (JP_VERSION) { ff8_externals.sub_4023D0 = get_relative_call(ff8_externals.sub_4023D0, 0x0); } - ff8_externals.sub_53C750 = get_relative_call(ff8_externals.sub_53FAC0, 0x2DC); - ff8_externals.sub_54FDA0 = get_relative_call(ff8_externals.sub_53FAC0, 0x376); - ff8_externals.sub_54D7E0 = get_relative_call(ff8_externals.sub_53FAC0, 0x3C4); - ff8_externals.sub_544630 = get_relative_call(ff8_externals.sub_53FAC0, 0x3D5); - ff8_externals.sub_545EA0 = get_relative_call(ff8_externals.sub_53FAC0, 0x4C1); + ff8_externals.sub_53C750 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x2DC); + ff8_externals.sub_54FDA0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x376); + ff8_externals.sub_54D7E0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x3C4); + ff8_externals.sub_544630 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x3D5); + ff8_externals.sub_545EA0 = get_relative_call(ff8_externals.worldmap_with_fog_sub_53FAC0, 0x4C1); ff8_externals.sub_545F10 = get_relative_call(ff8_externals.sub_545EA0, 0x1C); @@ -542,7 +571,7 @@ void ff8_find_externals() ff8_externals.sub_54A0D0 = 0x54A0D0; - ff8_externals.battle_trigger_worldmap = ff8_externals.sub_53FAC0 + 0x4EA; + ff8_externals.battle_trigger_worldmap = ff8_externals.worldmap_with_fog_sub_53FAC0 + 0x4EA; } ff8_externals.sub_548080 = get_relative_call(ff8_externals.worldmap_sub_53F310_loc_53F7EE, 0x9B); diff --git a/src/ff8_opengl.cpp b/src/ff8_opengl.cpp index 9126b61a..8f4682b7 100644 --- a/src/ff8_opengl.cpp +++ b/src/ff8_opengl.cpp @@ -190,8 +190,8 @@ struct char *image_data; uint32_t size; struct ff8_texture_set *texture_set; -} reload_buffer[TEXRELOAD_BUFFER_SIZE]; -uint32_t reload_buffer_index; +} reload_buffer[TEXRELOAD_BUFFER_SIZE] = {}; +uint32_t reload_buffer_index = 0; // this function is wedged into the middle of a function designed to reload a Direct3D texture // when the image data changes @@ -208,15 +208,9 @@ void texture_reload_hack(struct ff8_texture_set *texture_set) // unnecessary texture reloads for(i = 0; i < TEXRELOAD_BUFFER_SIZE; i++) { - if(reload_buffer[i].texture_set == texture_set) + if(reload_buffer[i].texture_set == texture_set && reload_buffer[i].size == size && memcmp(reload_buffer[i].image_data, VREF(tex_header, image_data), size) == 0) { - if(reload_buffer[i].size == size) - { - if(!memcmp(reload_buffer[i].image_data, VREF(tex_header, image_data), size)) - { - return; - } - } + return; } } @@ -224,8 +218,13 @@ void texture_reload_hack(struct ff8_texture_set *texture_set) common_load_texture((struct texture_set *)texture_set, texture_set->tex_header, texture_set->texture_format); reload_buffer[reload_buffer_index].texture_set = texture_set; - driver_free(reload_buffer[reload_buffer_index].image_data); - reload_buffer[reload_buffer_index].image_data = (char*)driver_malloc(size); + if (reload_buffer[reload_buffer_index].image_data != nullptr && reload_buffer[reload_buffer_index].size != size) { + driver_free(reload_buffer[reload_buffer_index].image_data); + reload_buffer[reload_buffer_index].image_data = nullptr; + } + if (reload_buffer[reload_buffer_index].image_data == nullptr) { + reload_buffer[reload_buffer_index].image_data = (char*)driver_malloc(size); + } memcpy(reload_buffer[reload_buffer_index].image_data, VREF(tex_header, image_data), size); reload_buffer[reload_buffer_index].size = size; reload_buffer_index = (reload_buffer_index + 1) % TEXRELOAD_BUFFER_SIZE; diff --git a/src/gl/special_case.cpp b/src/gl/special_case.cpp index 16a6ed2f..67110f3f 100644 --- a/src/gl/special_case.cpp +++ b/src/gl/special_case.cpp @@ -54,6 +54,9 @@ uint32_t gl_special_case(uint32_t primitivetype, uint32_t vertextype, struct nve // some modpath textures have filtering forced on if(current_state.texture_set && VREF(texture_set, ogl.gl_set->force_filter) && VREF(texture_set, ogl.external)) current_state.texture_filter = true; + // Texture filtering mostly does not work well in FF8 + if(ff8) current_state.texture_filter = false; + // some modpath textures have z-sort forced on if(current_state.texture_set && VREF(texture_set, ogl.gl_set->force_zsort) && VREF(texture_set, ogl.external)) defer = true; diff --git a/src/image/tim.cpp b/src/image/tim.cpp index 6e1ad04e..050edd6f 100644 --- a/src/image/tim.cpp +++ b/src/image/tim.cpp @@ -26,6 +26,40 @@ #include "../log.h" #include "../saveload.h" +TimRect::TimRect() : + palIndex(0), x1(0), y1(0), x2(0), y2(0) +{ +} + +TimRect::TimRect(uint32_t palIndex, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) : + palIndex(palIndex), x1(x1), y1(y1), x2(x2), y2(y2) +{ +} + +bool TimRect::match(uint32_t x, uint32_t y) const +{ + return x >= x1 && x <= x2 && y >= y1 && y <= y2; +} + +bool TimRect::isValid() const +{ + return x1 != x2 || y1 != y2; +} + +int operator==(const TimRect &rect, const TimRect &other) +{ + return rect.palIndex == other.palIndex + && rect.x1 == other.x1 + && rect.y1 == other.y1 + && rect.x2 == other.x2 + && rect.y2 == other.y2; +} + +bool operator<(const TimRect &rect, const TimRect &other) +{ + return ((uint64_t(rect.palIndex) << 56) | (uint64_t(rect.x1) << 28) | uint64_t(rect.y1)) < ((uint64_t(other.palIndex) << 56) | (uint64_t(other.x1) << 28) | uint64_t(other.y1)); +} + Tim::Tim(Bpp bpp, const ff8_tim &tim) : _bpp(bpp), _tim(tim) { @@ -116,6 +150,41 @@ bool Tim::toRGBA32MultiPaletteGrid(uint32_t *target, uint8_t cellCols, uint8_t c return grid.isValid() && toRGBA32(target, &grid, withAlpha); } + + +PaletteDetectionStrategyTrianglesAndQuads::PaletteDetectionStrategyTrianglesAndQuads(const Tim *const tim, const std::vector &rectangles) : + PaletteDetectionStrategy(tim), _rectangles(rectangles) +{ +} + +uint32_t PaletteDetectionStrategyTrianglesAndQuads::palOffset(uint16_t imgX, uint16_t imgY) const +{ + for (const TimRect &rectangle: _rectangles) { + if (rectangle.match(imgX, imgY)) { + return rectangle.palIndex * _tim->_tim.pal_w; + } + } + + return 0; +} + +uint32_t PaletteDetectionStrategyTrianglesAndQuads::palIndex() const +{ + return 0; +} + +bool Tim::saveMultiPaletteTrianglesAndQuads(const char *fileName, const std::vector &rectangles, bool withAlpha) const +{ + PaletteDetectionStrategyTrianglesAndQuads strategy(this, rectangles); + return strategy.isValid() && save(fileName, &strategy, withAlpha); +} + +bool Tim::toRGBA32MultiPaletteTrianglesAndQuads(uint32_t *target, const std::vector &rectangles, bool withAlpha) const +{ + PaletteDetectionStrategyTrianglesAndQuads strategy(this, rectangles); + return strategy.isValid() && toRGBA32(target, &strategy, withAlpha); +} + bool Tim::toRGBA32(uint32_t *target, PaletteDetectionStrategy *paletteDetectionStrategy, bool withAlpha) const { if (_tim.img_data == nullptr) diff --git a/src/image/tim.h b/src/image/tim.h index 4923bf4e..2af744ae 100644 --- a/src/image/tim.h +++ b/src/image/tim.h @@ -23,8 +23,22 @@ #pragma once #include +#include #include "../ff8.h" +struct TimRect { + TimRect(); + TimRect(uint32_t palIndex, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2); + bool match(uint32_t x, uint32_t y) const; + bool isValid() const; + uint32_t palIndex; + uint32_t x1, y1; + uint32_t x2, y2; +}; + +int operator==(const TimRect &rectangle, const TimRect &other); +bool operator<(const TimRect &rectangle, const TimRect &other); + inline uint32_t fromR5G5B5Color(uint16_t color, bool withAlpha = false) { uint8_t r = color & 0x1F, @@ -81,9 +95,19 @@ class PaletteDetectionStrategyGrid : public PaletteDetectionStrategy { uint8_t _palCols; }; +class PaletteDetectionStrategyTrianglesAndQuads : public PaletteDetectionStrategy { +public: + PaletteDetectionStrategyTrianglesAndQuads(const Tim *const tim, const std::vector &rectangles); + virtual uint32_t palOffset(uint16_t imgX, uint16_t imgY) const override; + virtual uint32_t palIndex() const override; +private: + const std::vector &_rectangles; +}; + class Tim { friend class PaletteDetectionStrategyFixed; friend class PaletteDetectionStrategyGrid; + friend class PaletteDetectionStrategyTrianglesAndQuads; public: enum Bpp { Bpp4 = 0, @@ -125,11 +149,19 @@ class Tim { const char *fileName, uint8_t cellCols, uint8_t cellRows, uint8_t colorsPerPal = 0, uint8_t palColsPerRow = 1, bool withAlpha = false ) const; + bool saveMultiPaletteTrianglesAndQuads( + const char *fileName, const std::vector &rectangles, + bool withAlpha = false + ) const; bool toRGBA32(uint32_t *target, uint8_t palX = 0, uint8_t palY = 0, bool withAlpha = false) const; bool toRGBA32MultiPaletteGrid( uint32_t *target, uint8_t cellCols, uint8_t cellRows, uint8_t colorsPerPal = 0, uint8_t palColsPerRow = 1, bool withAlpha = false ) const; + bool toRGBA32MultiPaletteTrianglesAndQuads( + uint32_t *target, const std::vector &rectangles, + bool withAlpha = false + ) const; static Tim fromLzsData(const uint8_t *uncompressed_data); static Tim fromTimData(const uint8_t *data); private: diff --git a/src/patch.cpp b/src/patch.cpp index c1777fc0..60dab891 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -36,13 +36,15 @@ uint32_t max_addr = 0; uint32_t replace_counter = 0; uint32_t replaced_functions[512 * 3]; -void check_is_call(const char *name, uint32_t base, uint32_t offset, uint8_t instruction) +uint8_t check_is_call(const char *name, uint32_t base, uint32_t offset, uint16_t instruction) { - if (instruction != 0xE8 && instruction != 0xE9) + if ((instruction & 0xFF) != 0xE8 && (instruction & 0xFF) != 0xE9 && instruction != 0x15FF) { // Warning to diagnose errors faster ffnx_warning("%s: Unrecognized call/jmp instruction at 0x%X + 0x%X (0x%X): 0x%X\n", name, base, offset, base + offset, instruction); } + + return instruction == 0x15FF ? 2 : 1; } #ifdef PATCH_COLLECT_DUPLICATES @@ -144,11 +146,11 @@ void replace_call(uint32_t offset, void *func) { DWORD dummy; - check_is_call(__func__, offset, 0, *((uint8_t *)(offset))); + uint8_t size = check_is_call(__func__, offset, 0, *((uint16_t *)(offset))); - VirtualProtect((void *)offset, 5, PAGE_EXECUTE_READWRITE, &dummy); + VirtualProtect((void *)offset, size + 4, PAGE_EXECUTE_READWRITE, &dummy); - *(uint32_t *)(offset + 1) = ((uint32_t)func - offset) - 5; + *(uint32_t *)(offset + size) = ((uint32_t)func - offset) - (size + 4); } uint32_t replace_call_function(uint32_t offset, void* func) @@ -169,11 +171,11 @@ uint32_t replace_call_function(uint32_t offset, void* func) uint32_t get_relative_call(uint32_t base, uint32_t offset) { - uint8_t instruction = *((uint8_t *)(base + offset)); + uint16_t instruction = *((uint16_t *)(base + offset)); - check_is_call(__func__, base, offset, instruction); + uint8_t size = check_is_call(__func__, base, offset, instruction); - uint32_t ret = base + *((uint32_t *)(base + offset + 1)) + offset + 5; + uint32_t ret = base + *((uint32_t *)(base + offset + size)) + offset + 4 + size; #ifdef PATCH_COLLECT_DUPLICATES collect_addresses(__func__, base, offset, ret);