Skip to content

Commit

Permalink
Modding: Allow replacing EXE text/data hardcoded in code (julianxhoka…
Browse files Browse the repository at this point in the history
  • Loading branch information
myst6re authored Jun 1, 2024
1 parent 73923c9 commit b2db930
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 2 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Audio: Fix bug that won't allow to configure vanilla SFX IDs using the `sfx/config.toml` file
- Audio: Fix bug where overriding only fade flags in `ambient/config.toml` would not allow the ambient audio file to be loaded
- External textures: Reuse already loaded textures on fallback to palette 0 ( https://github.com/julianxhokaxhiu/FFNx/pull/692 )
- Modding: Allow replacing EXE text/data hardcoded in code ( https://github.com/julianxhokaxhiu/FFNx/pull/699 )
- Rendering: Add bilinear filtering option `enable_bilinear` ( https://github.com/julianxhokaxhiu/FFNx/pull/692 )

## FF7
Expand Down
2 changes: 2 additions & 0 deletions docs/ff8/mods/external_textures.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ If not specified, the game will always fallback to the path with palette index e
You can add the language at the beginning of the path for localization:<br>
`{mod_path}\fre\cardgame\cards_00.dds`

Available language suffixes: eng (English), fre (French), ger (German), ita (Italian), jp (Japanese), spa (Spanish).

If you mod __by VRAM page__, file names look like this:<br>
`{mod_path}\cardgame\cards_{relative vram page}_{palette x}_{palette y}.dds`<br>
If a texture is not found, the game will always fallback to the path with zeroed palette x and palette y:<br>
Expand Down
11 changes: 11 additions & 0 deletions docs/mods/direct_mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Direct mode

The Direct mode allows modders to bypass game archives
(LGP for FF7 or FS/FI/FL for FF8) and read files directly from the directory
pointed by the `direct_mode_path` configuration entry.

For example: if FF7 is looking for aaab.rsd in char.lgp, this mode will make it open direct/char/aaab.rsd first,
If this file doesn't exist it will look for the original in the LGP archive
Another example: if FF8 is looking for c:/data/en/FIELD/mapdata/bc/bccent12/bccent12.msd in field.fs,
this mode will make it open direct/FIELD/mapdata/bc/bccent12/bccent12.msd if it exists.

18 changes: 18 additions & 0 deletions docs/mods/exe_data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# EXE data

Sometimes texts and textures are stored inside the EXE, instead in the data directory.
In this case it is harder for modders to mod the game.

This feature allows modders to override data from the EXE via the
(Direct Mode)[direct_mode.md] feature.

Use the `save_exe_data` option to dump files to the direct/exe/ directory.
And then FFNx will look for those files directly instead of data from the EXE.

## Supported data

### FF8

- `battle_scans.msd`: Texts in battle scans. Note: this is not exactly the same
format in the EXE, the msd format is used because it is a well documented format
of FF8.
2 changes: 2 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Welcome to the FFNx documentation!
## Modding

- [DevTools](mods/devtools.md)
- [Direct Mode](mods/direct_mode.md)
- [EXE Data](mods/exe_data.md)
- [Audio Engine](mods/audio_engine.md)
- [Video Encoding Guide](mods/video_encoding_guide.md)
- [External textures](mods/external_textures.md)
5 changes: 5 additions & 0 deletions misc/FFNx.toml
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ save_textures = false
#~~~~~~~~~~~~~~~~~~~~~~~~~~~
save_textures_legacy = false

# Dump identified data from EXE to "<direct_mode_path>/exe/"
# List of files dumped here: https://github.com/julianxhokaxhiu/FFNx/tree/master/docs/mods/exe_data.md
#~~~~~~~~~~~~~~~~~~~~~~~~~~~
save_exe_data = false

# This path is where the Hext patching layer will look for txt files.
# The path will ALWAYS have appended:
# 1. The game name ( if FF7 it will be "ff7/", if FF8 will be "ff8/")
Expand Down
2 changes: 2 additions & 0 deletions src/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ bool enable_voice_auto_text;
bool enable_auto_run;
bool save_textures;
bool save_textures_legacy;
bool save_exe_data;
bool trace_all;
bool trace_renderer;
bool trace_movies;
Expand Down Expand Up @@ -212,6 +213,7 @@ void read_cfg()
external_mesh_path = config["external_mesh_path"].value_or("");
save_textures = config["save_textures"].value_or(false);
save_textures_legacy = config["save_textures_legacy"].value_or(false);
save_exe_data = config["save_exe_data"].value_or(false);
trace_all = config["trace_all"].value_or(false);
trace_renderer = config["trace_renderer"].value_or(false);
trace_movies = config["trace_movies"].value_or(false);
Expand Down
1 change: 1 addition & 0 deletions src/cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ extern long external_voice_music_fade_volume;
extern bool enable_voice_auto_text;
extern bool save_textures;
extern bool save_textures_legacy;
extern bool save_exe_data;
extern bool trace_all;
extern bool trace_renderer;
extern bool trace_movies;
Expand Down
3 changes: 3 additions & 0 deletions src/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include "lighting.h"
#include "achievement.h"
#include "game_cfg.h"
#include "exe_data.h"

#include "ff7/widescreen.h"
#include "ff7/time.h"
Expand Down Expand Up @@ -920,6 +921,8 @@ int common_create_window(HINSTANCE hInstance, struct game_obj* game_object)
vibration_init();
}

exe_data_init();

// Init Day Night Cycle
if (!ff8 && enable_time_cycle) ff7::time.init();

Expand Down
157 changes: 157 additions & 0 deletions src/exe_data.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/****************************************************************************/
// Copyright (C) 2009 Aali132 //
// Copyright (C) 2018 quantumpencil //
// Copyright (C) 2018 Maxime Bacoux //
// Copyright (C) 2024 myst6re //
// Copyright (C) 2020 Chris Rizzitello //
// Copyright (C) 2020 John Pritchard //
// Copyright (C) 2024 Julian Xhokaxhiu //
// //
// 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 "common.h"
#include "log.h"
#include "utils.h"
#include "patch.h"
#include "saveload.h"

uint8_t *ff8_exe_scan_texts = nullptr;

bool ff8_get_battle_scan_texts_filename(char *filename)
{
snprintf(filename, MAX_PATH, "%s/exe/battle_scans.msd", direct_mode_path.c_str());
normalize_path(filename);

return fileExists(filename);
}

void ff8_dump_battle_scan_texts()
{
uint8_t *data = (uint8_t *)ff8_externals.scan_text_data;
uint16_t *offsets_rel_to_data = (uint16_t *)ff8_externals.scan_text_positions;
int text_count = int((uint16_t *)data - offsets_rel_to_data);
uint16_t data_offset = text_count * sizeof(uint32_t);
uint32_t offsets_rel_to_start[0x200] = {};
int higher_offset = 0;

for (int i = 0; i < text_count; ++i) {
offsets_rel_to_start[i] = offsets_rel_to_data[i] + data_offset;
if (offsets_rel_to_data[i] > higher_offset) {
higher_offset = offsets_rel_to_data[i];
}
}

for (int i = higher_offset; i < higher_offset + 1024; ++i) {
if (data[i] == '\0') {
higher_offset = i + 1;
break;
}
}

char filename[MAX_PATH] = {};
if (ff8_get_battle_scan_texts_filename(filename)) {
ffnx_warning("Save exe file skipped because the file [ %s ] already exists.\n", filename);

return;
}

FILE *f = fopen(filename, "wb");

if (f == nullptr) {
return;
}

fwrite(offsets_rel_to_start, data_offset, 1, f);
fwrite(data, higher_offset, 1, f);
fclose(f);
}

uint8_t *ff8_override_battle_scans()
{
if (ff8_exe_scan_texts != nullptr) {
return ff8_exe_scan_texts;
}

char filename[MAX_PATH] = {};
if (! ff8_get_battle_scan_texts_filename(filename)) {
if (trace_all || trace_direct) ffnx_warning("Direct file not found %s\n", filename);

return nullptr;
}

if (trace_all || trace_direct) ffnx_info("Direct file using %s\n", filename);

FILE *f = fopen(filename, "rb");

if (f == nullptr) {
return nullptr;
}

fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);

ff8_exe_scan_texts = (uint8_t *)driver_malloc(file_size); // Allocated once, never freed

if (ff8_exe_scan_texts == nullptr) {
return nullptr;
}

fread(ff8_exe_scan_texts, file_size, 1, f);
fclose(f);

return ff8_exe_scan_texts;
}

uint8_t *ff8_battle_get_scan_text(uint8_t target_id)
{
uint8_t *direct_data_msd = ff8_override_battle_scans();

if (direct_data_msd != nullptr) {
uint32_t *positions = (uint32_t *)direct_data_msd;
uint8_t *entities = (uint8_t *)ff8_externals.battle_entities_1D27BCB;

if (trace_all) ffnx_trace("%s: get scan text target_id=%d entity_id=%d\n", __func__, target_id, entities[208 * target_id]);

return direct_data_msd + positions[entities[208 * target_id]];
}

return ((uint8_t*(*)(uint8_t))ff8_externals.scan_get_text_sub_B687C0)(target_id);
}

void dump_exe_data()
{
char dirname[MAX_PATH] = {};
snprintf(dirname, sizeof(dirname), "%s/exe/", direct_mode_path.c_str());

normalize_path(dirname);
make_path(dirname);

if (ff8)
{
ff8_dump_battle_scan_texts();
}
}

void exe_data_init()
{
if (save_exe_data)
{
dump_exe_data();
}

if (ff8)
{
replace_call(ff8_externals.sub_84F8D0 + 0x88, ff8_battle_get_scan_text);
}
}
24 changes: 24 additions & 0 deletions src/exe_data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/****************************************************************************/
// Copyright (C) 2009 Aali132 //
// Copyright (C) 2018 quantumpencil //
// Copyright (C) 2018 Maxime Bacoux //
// Copyright (C) 2024 myst6re //
// Copyright (C) 2020 Chris Rizzitello //
// Copyright (C) 2020 John Pritchard //
// Copyright (C) 2024 Julian Xhokaxhiu //
// //
// 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 exe_data_init();
12 changes: 12 additions & 0 deletions src/ff8.h
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,18 @@ struct ff8_externals
BYTE** dword_27973E8;
uint32_t battle_set_action_upload_raw_palette_sub_B666F0;
uint32_t battle_set_action_upload_raw_palette_sub_B66400;
uint32_t sub_84D110;
uint32_t sub_84D1F0;
uint32_t sub_84D230;
uint32_t sub_84D2C0;
uint32_t sub_84D4B0;
uint32_t sub_84F2A0;
uint32_t sub_84F860;
uint32_t sub_84F8D0;
uint32_t scan_get_text_sub_B687C0;
uint32_t battle_entities_1D27BCB;
uint32_t scan_text_data;
uint32_t scan_text_positions;
};

void ff8gl_field_78(struct ff8_polygon_set *polygon_set, struct ff8_game_obj *game_object);
Expand Down
1 change: 1 addition & 0 deletions src/ff8/battle/effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace FF8BattleEffect {
enum Effect {
Cure = 0,
Leviathan = 5,
Scan = 39,
Tonberry = 89,
Siren = 94,
Minimog = 95,
Expand Down
13 changes: 13 additions & 0 deletions src/ff8_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,19 @@ void ff8_find_externals()
ff8_externals.load_magic_data_sub_571900 = get_relative_call(ff8_externals.load_magic_data_sub_571B80, 0x1E);
ff8_externals.load_magic_data_sub_5718E0 = get_relative_call(ff8_externals.func_off_battle_effect_textures_50AF93[198], 0x5);

ff8_externals.sub_84D110 = get_absolute_value(ff8_externals.func_off_battle_effects_C81774[FF8BattleEffect::Scan], 0x28);
ff8_externals.sub_84D1F0 = get_absolute_value(ff8_externals.sub_84D110, 0x14);
ff8_externals.sub_84D230 = get_absolute_value(ff8_externals.sub_84D1F0, 0x9);
ff8_externals.sub_84D2C0 = get_absolute_value(ff8_externals.sub_84D230, 0xC);
ff8_externals.sub_84D4B0 = get_absolute_value(ff8_externals.sub_84D2C0, 0x3C);
ff8_externals.sub_84F2A0 = get_absolute_value(ff8_externals.sub_84D4B0, 0x39);
ff8_externals.sub_84F860 = get_absolute_value(ff8_externals.sub_84F2A0, 0x14);
ff8_externals.sub_84F8D0 = get_absolute_value(ff8_externals.sub_84F860, 0xD);
ff8_externals.scan_get_text_sub_B687C0 = get_relative_call(ff8_externals.sub_84F8D0, 0x88);
ff8_externals.battle_entities_1D27BCB = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x18);
ff8_externals.scan_text_positions = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x20);
ff8_externals.scan_text_data = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x27);

// Required by Steam edition
switch (version)
{
Expand Down
4 changes: 2 additions & 2 deletions src/saveload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ std::map<uint16_t, std::string> additional_textures = {
{RendererTextureSlot::TEX_PBR, "pbr"}
};

void make_path(char *name)
void make_path(const char *name)
{
char *next = name;
const char *next = name;

while((next = strchr(next, '/')))
{
Expand Down
2 changes: 2 additions & 0 deletions src/saveload.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@

#pragma once

void make_path(const char *name);
void normalize_path(char *name);
void save_texture(const void *data, uint32_t dataSize, uint32_t width, uint32_t height, uint32_t palette_index, const char *name, bool is_animated);
uint32_t load_texture(const void *data, uint32_t dataSize, const char *name, uint32_t palette_index, uint32_t *width, uint32_t *height, struct gl_texture_set* gl_set);

0 comments on commit b2db930

Please sign in to comment.