From 045d6215613cfdeecd5d1a0df27b475186452319 Mon Sep 17 00:00:00 2001 From: myst6re Date: Sun, 22 Feb 2026 22:43:04 +0100 Subject: [PATCH 1/2] FF8: Add chunk feature for battle ennemies c0mXXX.dat files --- src/ff8/vram.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/ff8/vram.cpp b/src/ff8/vram.cpp index e4202562..2436412c 100644 --- a/src/ff8/vram.cpp +++ b/src/ff8/vram.cpp @@ -1411,7 +1411,7 @@ int battle_get_texture_file_name_index(void *texture_buffer) int16_t ff8_battle_open_and_read_file(int fileId, void *data, int a3, int callback) { - if (trace_all || trace_vram) ffnx_trace("%s: %d => %s\n", __func__, fileId, ff8_externals.battle_filenames[fileId]); + if (trace_all || trace_files) ffnx_trace("%s: %d => %s\n", __func__, fileId, ff8_externals.battle_filenames[fileId]); battle_texture_id = 0; if (stricmp(ff8_externals.battle_filenames[fileId], "B0WAVE.DAT") == 0) { @@ -1439,7 +1439,72 @@ int16_t ff8_battle_open_and_read_file(int fileId, void *data, int a3, int callba battle_texture_data_list[index] = tex; } - return ((int16_t(*)(int,void*,int,int))ff8_externals.battle_open_file)(fileId, data, a3, callback); + int16_t ret = ((int16_t(*)(int,void*,int,int))ff8_externals.battle_open_file)(fileId, data, a3, callback); + + // c0mXXX.dat files (battle ennemies) + if (fileId >= 166 && fileId <= 309 && *(uint32_t *)data == 11) { + char chunk_file[1024]{0}; + byte *chunks_data[11]{nullptr}; + long chunks_size[11]{0}; + bool has_chunk = false; + + for (int chunkId = 0; chunkId < 11; ++chunkId) { + _snprintf(chunk_file, sizeof(chunk_file), "%s/%s/battle/%s.chunk.%i", basedir, direct_mode_path.c_str(), ff8_externals.battle_filenames[fileId], chunkId + 1); + + FILE *fd = fopen(chunk_file, "rb"); + + if (fd == nullptr) { + if (trace_all || trace_direct) ffnx_warning("Direct file not found %s\n", chunk_file); + + continue; + } + + if (trace_all || trace_direct) ffnx_info("Direct file using %s\n", chunk_file); + + fseek(fd, 0L, SEEK_END); + long chunk_size = ftell(fd); + fseek(fd, 0L, SEEK_SET); + + byte *chunk_data = new byte[chunk_size]; + + fread(chunk_data, sizeof(byte), chunk_size, fd); + fclose(fd); + + chunks_data[chunkId] = chunk_data; + chunks_size[chunkId] = chunk_size; + has_chunk = true; + } + + // Rebuild file content with chunks + original data + if (has_chunk) { + uint32_t *toc = (uint32_t *)data + 1; + uint32_t cur_data_pos = (11 + 2) * sizeof(uint32_t); + byte *old_data = new byte[toc[12]]; + memcpy(old_data, data, toc[12] * sizeof(byte)); + uint32_t *old_toc = (uint32_t *)old_data + 1; + + for (int chunkId = 0; chunkId < 11; ++chunkId) { + byte *chunk_data = chunks_data[chunkId]; + long chunk_size; + toc[chunkId] = cur_data_pos; + if (chunk_data != nullptr) { + chunk_size = chunks_size[chunkId]; + memcpy((uint8_t *)data + toc[chunkId], chunk_data, chunks_size[chunkId]); + delete[] chunk_data; + } else { + chunk_size = old_toc[chunkId + 1] - old_toc[chunkId]; + memcpy((uint8_t *)data + toc[chunkId], old_data + old_toc[chunkId], chunk_size); + } + cur_data_pos += chunk_size; + } + + toc[11] = cur_data_pos; + + delete[] old_data; + } + } + + return ret; } void *ff8_battle_open_effect(const char *fileName, void *data, int dataSize, DWORD *outSize) From 8f35d7d433be977dbc7cac38aa0a2e63a498f9f0 Mon Sep 17 00:00:00 2001 From: myst6re Date: Mon, 11 Apr 2022 17:58:04 +0200 Subject: [PATCH 2/2] FF8: Remastered compatibility (unstable) --- .github/workflows/build.ps1 | 15 + CMakeLists.txt | 86 +++++- docs/how_to_install.md | 17 +- misc/FFNx-Remastered-Proxy.def | 25 ++ misc/FFNx.toml | 19 +- remastered-proxy/main.cpp | 76 +++++ src/achievement.cpp | 8 +- src/achievement.h | 2 +- src/audio/vgmstream/vgmstream.cpp | 19 +- src/audio/vgmstream/zzzstreamfile.cpp | 197 +++++++++++++ src/audio/vgmstream/zzzstreamfile.h | 35 +++ src/cfg.cpp | 7 + src/cfg.h | 2 + src/common.cpp | 239 ++++++++------- src/common.h | 4 +- src/ff8.h | 38 +++ src/ff8/field/chara_one.cpp | 1 + src/ff8/field/chara_one.h | 1 + src/ff8/file.cpp | 399 +++++++++++++++++++++++++- src/ff8/file.h | 11 + src/ff8/mod.cpp | 82 +++++- src/ff8/mod.h | 2 + src/ff8/movies.cpp | 83 ++++++ src/ff8/movies.h | 6 + src/ff8/remaster.cpp | 387 +++++++++++++++++++++++++ src/ff8/remaster.h | 32 +++ src/ff8/texture_packer.cpp | 24 +- src/ff8/texture_packer.h | 10 +- src/ff8/vram.cpp | 109 ++++++- src/ff8/vram.h | 3 + src/ff8/zzz_archive.cpp | 270 +++++++++++++++++ src/ff8/zzz_archive.h | 99 +++++++ src/ff8_data.cpp | 41 +++ src/ff8_opengl.cpp | 57 +++- src/game_cfg.cpp | 62 +++- src/globals.h | 1 + src/image/image.cpp | 34 ++- src/movies.cpp | 107 ++++--- src/music.cpp | 27 ++ src/saveload.cpp | 4 + src/steam.cpp | 217 ++++++++++++++ src/steam.h | 112 ++++++++ src/world.cpp | 2 +- 43 files changed, 2754 insertions(+), 218 deletions(-) create mode 100644 misc/FFNx-Remastered-Proxy.def create mode 100644 remastered-proxy/main.cpp create mode 100644 src/audio/vgmstream/zzzstreamfile.cpp create mode 100644 src/audio/vgmstream/zzzstreamfile.h create mode 100644 src/ff8/remaster.cpp create mode 100644 src/ff8/remaster.h create mode 100644 src/ff8/zzz_archive.cpp create mode 100644 src/ff8/zzz_archive.h create mode 100644 src/steam.cpp create mode 100644 src/steam.h diff --git a/.github/workflows/build.ps1 b/.github/workflows/build.ps1 index fc0edc67..dad8520c 100644 --- a/.github/workflows/build.ps1 +++ b/.github/workflows/build.ps1 @@ -84,23 +84,38 @@ cmake --build --preset "${env:_RELEASE_CONFIGURATION}" # Start the packaging mkdir .dist\pkg\FF7_1998 | Out-Null mkdir .dist\pkg\FF8_2000 | Out-Null +mkdir .dist\pkg\FF8_Remastered | Out-Null mkdir .dist\pkg\FFNx_Steam | Out-Null Copy-Item -R "$releasePath\bin\*" .dist\pkg\FF7_1998 Copy-Item -R "$releasePath\bin\*" .dist\pkg\FF8_2000 +Copy-Item -R "$releasePath\bin\*" .dist\pkg\FF8_Remastered Copy-Item -R "$releasePath\bin\*" .dist\pkg\FFNx_Steam Remove-Item .dist\pkg\FF7_1998\FF8.reg +Remove-Item .dist\pkg\FF7_1998\ffnx_steam_api.dll Remove-Item .dist\pkg\FF8_2000\FF7.reg +Remove-Item .dist\pkg\FF8_2000\ffnx_steam_api.dll +Remove-Item .dist\pkg\FF8_Remastered\FF7.reg Remove-Item .dist\pkg\FFNx_Steam\FF7.reg Remove-Item .dist\pkg\FFNx_Steam\FF8.reg +Remove-Item .dist\pkg\FFNx_Steam\FFVIII_EFIGS.dll +Remove-Item .dist\pkg\FFNx_Steam\FFVIII_JP.dll Remove-Item .dist\pkg\FF7_1998\AF4DN.P +Remove-Item .dist\pkg\FF7_1998\FFVIII_EFIGS.dll +Remove-Item .dist\pkg\FF7_1998\FFVIII_JP.dll Remove-Item .dist\pkg\FF8_2000\AF4DN.P +Remove-Item .dist\pkg\FF8_2000\FFVIII_EFIGS.dll +Remove-Item .dist\pkg\FF8_2000\FFVIII_JP.dll +Remove-Item .dist\pkg\FF8_Remastered\AF4DN.P Move-Item .dist\pkg\FF7_1998\FF7.reg .dist\pkg\FF7_1998\FFNx.reg Move-Item .dist\pkg\FF8_2000\FF8.reg .dist\pkg\FF8_2000\FFNx.reg +Move-Item .dist\pkg\FF8_Remastered\FF8.reg .dist\pkg\FF8_Remastered\FFNx.reg Move-Item .dist\pkg\FF8_2000\FFNx.dll .dist\pkg\FF8_2000\eax.dll +Move-Item .dist\pkg\FF8_Remastered\FFNx.dll .dist\pkg\FF8_Remastered\eax.dll Move-Item .dist\pkg\FFNx_Steam\FFNx.dll .dist\pkg\FFNx_Steam\AF3DN.P 7z a ".\.dist\${env:_RELEASE_NAME}-FF7_1998-${env:_RELEASE_VERSION}.zip" ".\.dist\pkg\FF7_1998\*" 7z a ".\.dist\${env:_RELEASE_NAME}-FF8_2000-${env:_RELEASE_VERSION}.zip" ".\.dist\pkg\FF8_2000\*" +7z a ".\.dist\${env:_RELEASE_NAME}-FF8_Remastered-${env:_RELEASE_VERSION}.zip" ".\.dist\pkg\FF8_Remastered\*" 7z a ".\.dist\${env:_RELEASE_NAME}-Steam-${env:_RELEASE_VERSION}.zip" ".\.dist\pkg\FFNx_Steam\*" Remove-Item -Recurse -Force .dist\pkg diff --git a/CMakeLists.txt b/CMakeLists.txt index 81157bcd..192950f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,30 @@ target_compile_features(${CHOCO_RELEASE_NAME} ) target_link_options(${CHOCO_RELEASE_NAME} PRIVATE /PDBALTPATH:${CHOCO_RELEASE_NAME}.pdb PRIVATE /DEF:${CMAKE_SOURCE_DIR}/misc/${CHOCO_RELEASE_NAME}.def) +# Remastered proxy +set(REMASTERED_RELEASE_NAME "FFNx-Remastered-Proxy") +file(GLOB_RECURSE remastered_source_files "${CMAKE_SOURCE_DIR}/remastered-proxy/*.cpp") +add_library(${REMASTERED_RELEASE_NAME} SHARED ${remastered_source_files} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +target_include_directories( + ${REMASTERED_RELEASE_NAME} + PRIVATE "${CMAKE_SOURCE_DIR}/remastered-proxy" +) +target_compile_options( + ${REMASTERED_RELEASE_NAME} + PRIVATE /DVERSION="${_DLL_VERSION}" + PRIVATE /D_CRT_SECURE_NO_WARNINGS + PRIVATE /DNOMINMAX + PRIVATE /Zc:strictStrings- + PRIVATE /Zc:__cplusplus + PRIVATE /Zc:preprocessor + PRIVATE /Qpar + PRIVATE /MP +) +target_compile_features(${REMASTERED_RELEASE_NAME} + PRIVATE cxx_std_20 +) +target_link_options(${REMASTERED_RELEASE_NAME} PRIVATE /PDBALTPATH:${REMASTERED_RELEASE_NAME}.pdb PRIVATE /DEF:${CMAKE_SOURCE_DIR}/misc/${REMASTERED_RELEASE_NAME}.def) + # Include all the source code files file(GLOB_RECURSE source_files "${CMAKE_SOURCE_DIR}/src/*.cpp") @@ -148,7 +172,7 @@ cmrc_add_resource_library( ) add_library(${RELEASE_NAME} SHARED ${source_files} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) -add_dependencies(${RELEASE_NAME} ${CHOCO_RELEASE_NAME}) +add_dependencies(${RELEASE_NAME} ${CHOCO_RELEASE_NAME} ${REMASTERED_RELEASE_NAME}) target_include_directories( ${RELEASE_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/src" @@ -186,7 +210,6 @@ target_link_libraries( imgui::imgui pugixml::pugixml tomlplusplus::tomlplusplus - STEAMWORKSSDK::STEAMWORKSSDK xxHash::xxhash lz4::lz4 Vorbis::vorbisfile @@ -307,10 +330,21 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_BINARY_DIR}/bin/${CHOCO_RELEASE_NAME}.dll ${CMAKE_BINARY_DIR}/bin/AF4DN.P + # remastered proxy .dll + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${REMASTERED_RELEASE_NAME}.dll + ${CMAKE_BINARY_DIR}/bin + # rename remastered .dll to FFVIII_EFIGS.dll + COMMAND ${CMAKE_COMMAND} -E rename + ${CMAKE_BINARY_DIR}/bin/${REMASTERED_RELEASE_NAME}.dll + ${CMAKE_BINARY_DIR}/bin/FFVIII_EFIGS.dll + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/bin/FFVIII_EFIGS.dll + ${CMAKE_BINARY_DIR}/bin/FFVIII_JP.dll # steam_api.dll COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/vcpkg_installed/x86-windows-static/tools/steamworkssdk/steam_api.dll - ${CMAKE_BINARY_DIR}/bin + ${CMAKE_BINARY_DIR}/bin/ffnx_steam_api.dll # .pdb COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${RELEASE_NAME}.pdb @@ -610,3 +644,49 @@ if(NOT "${FF7_STEAM_RERELEASE_GAME_PATH}" STREQUAL "") ${FF7_STEAM_RERELEASE_GAME_PATH}/FFNx.toml ) endif() + +# Copy FFNx release to FF8 Remastered Steam game path if installed +execute_process( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/utils/FindSteamGamePath 1026680 + OUTPUT_VARIABLE FF8_REMASTERED_GAME_PATH + ERROR_VARIABLE FF8_REMASTERED_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(NOT "${FF8_REMASTERED_GAME_PATH}" STREQUAL "") + cmake_path(CONVERT "${FF8_REMASTERED_GAME_PATH}" TO_CMAKE_PATH_LIST FF8_REMASTERED_GAME_PATH) + # Ensure FFNx.toml exists so the copy on build does not error on fresh installs + if(NOT EXISTS "${FF8_REMASTERED_GAME_PATH}/FFNx.toml") + # Copy file if it doesn't exist + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/misc/FFNx.toml" DESTINATION "${FF8_REMASTERED_GAME_PATH}/") + endif() + add_custom_command( + TARGET ${RELEASE_NAME} + POST_BUILD + COMMAND echo Copying ${RELEASE_NAME} ${_DLL_VERSION} release to ${FF8_REMASTERED_GAME_PATH}... + # Preserve the current FFNx.toml + COMMAND ${CMAKE_COMMAND} -E rename + ${FF8_REMASTERED_GAME_PATH}/FFNx.toml + ${FF8_REMASTERED_GAME_PATH}/FFNx.toml.bak + # Prevent steam_api.dll to be deployed + COMMAND ${CMAKE_COMMAND} -E remove + ${CMAKE_BINARY_DIR}/bin/steam_api.dll + # Copy all dist files + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_BINARY_DIR}/bin + ${FF8_REMASTERED_GAME_PATH} + # Delete previous eax.dll + COMMAND ${CMAKE_COMMAND} -E remove + ${FF8_REMASTERED_GAME_PATH}/eax.dll + # Rename FFNx.dll to eax.dll + COMMAND ${CMAKE_COMMAND} -E rename + ${FF8_REMASTERED_GAME_PATH}/FFNx.dll + ${FF8_REMASTERED_GAME_PATH}/eax.dll + # Delete the new copied FFNx.toml + COMMAND ${CMAKE_COMMAND} -E remove + ${FF8_REMASTERED_GAME_PATH}/FFNx.toml + # Bring back the existing FFNx.toml + COMMAND ${CMAKE_COMMAND} -E rename + ${FF8_REMASTERED_GAME_PATH}/FFNx.toml.bak + ${FF8_REMASTERED_GAME_PATH}/FFNx.toml + ) +endif() diff --git a/docs/how_to_install.md b/docs/how_to_install.md index 0ed60ce6..7cb53464 100644 --- a/docs/how_to_install.md +++ b/docs/how_to_install.md @@ -86,7 +86,7 @@ external_music_ext = "akb" #### [2000 Squaresoft Release](https://www.mobygames.com/game/windows/final-fantasy-viii) 0. Install the game on this path: `C:\Games\Final Fantasy VIII` -1. Update your game to v1.2 ( you can find a collection here: https://www.ff8.fr/telechargements/programmes ) +1. Update your game to v1.2 ( you can find a collection here: [FF8.fr download page](https://www-ff8-fr.translate.goog/telechargements/programmes?_x_tr_sl=fr&_x_tr_tl=en&_x_tr_hl=fr&_x_tr_pto=wapp#archives_conserve_pour_la_posterite) ) 2. Download the latest `FFNx-FF8_2000` release here: https://github.com/julianxhokaxhiu/FFNx/releases 3. Extract the ZIP content next to `ff8.exe` file 4. Double click on [`FFNx.reg`](https://github.com/julianxhokaxhiu/FFNx/blob/master/misc/FF8.reg) @@ -102,6 +102,21 @@ external_music_ext = "akb" 5. Replace all files when asked. 6. Enjoy! +#### Early alpha [Remastered Release](https://store.steampowered.com/app/1026680/FINAL_FANTASY_VIII__REMASTERED/) + +**Warning:** The compatibilty with the Remastered version is currently uncomplete. You will most likely encounters crashes +in battle and model rendering issues. + +0. Install the game using Steam +1. Open the installation directory of the game ( see [How to access game files](https://steamcommunity.com/sharedfiles/filedetails/?id=760447682) ) +2. Download the FF8 patch v1.2 ( you can find a collection here: [FF8.fr download page](https://www-ff8-fr.translate.goog/telechargements/programmes?_x_tr_sl=fr&_x_tr_tl=en&_x_tr_hl=fr&_x_tr_pto=wapp#archives_conserve_pour_la_posterite) ) +3. Extract the patch ZIP content next to `FFVIII_LAUNCHER.exe` file +4. Rename `FF8.exe` to `FF8.ffnx` +5. Download the latest `FFNx-FF8_Remastered` release here: https://github.com/julianxhokaxhiu/FFNx/releases +6. Extract the FFNx ZIP content next to `FFVIII_LAUNCHER.exe` file +7. Replace all files when asked. +8. Enjoy! + ## Mod Launchers ### 7thHeaven 2.4.0+ diff --git a/misc/FFNx-Remastered-Proxy.def b/misc/FFNx-Remastered-Proxy.def new file mode 100644 index 00000000..07dccb5e --- /dev/null +++ b/misc/FFNx-Remastered-Proxy.def @@ -0,0 +1,25 @@ +;*****************************************************************************; +; Copyright (C) 2009 Aali132 ; +; Copyright (C) 2018 quantumpencil ; +; Copyright (C) 2018 Maxime Bacoux ; +; Copyright (C) 2020 myst6re ; +; Copyright (C) 2020 Chris Rizzitello ; +; Copyright (C) 2020 John Pritchard ; +; Copyright (C) 2025 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. ; +;*****************************************************************************; + +LIBRARY FFVIII_EFIGS.dll +EXPORTS +; Required for the Remastered driver injection +runGame diff --git a/misc/FFNx.toml b/misc/FFNx.toml index e766d7f9..6feafda5 100644 --- a/misc/FFNx.toml +++ b/misc/FFNx.toml @@ -38,7 +38,7 @@ borderless = false #[RESOLUTION] # Resolution of the game. # Default (value = 0): -# - Window mode will use 640x480 +# - Window mode will use 640x480 (1024x768 on FF8:Remastered) # - Fullscreen mode will use your desktop resolution #~~~~~~~~~~~~~~~~~~~~~~~~~~~ window_size_x = 0 @@ -262,7 +262,7 @@ external_music_sync = false # - FF7 1998: music/vgmstream ( 7h-era compatibility ) # - FF7 eStore: data/music_ogg # - FF7 Steam: data/music_ogg -# - FF8 2000/Steam: data/music/dmusic/ogg +# - FF8: data/music/dmusic/ogg #~~~~~~~~~~~~~~~~~~~~~~~~~~~ external_music_path = "" @@ -773,12 +773,25 @@ data_drive = "" #[HIGH RES FONT] # Overrides the high res font option if set. # Available choices are: -# - -1: Default game behavior (High-res on FF8 Steam, honor FF8Config option on FF8 2000) +# - -1: Default game behavior (HD font on remastered, High-res on FF8 Steam, honor FF8Config option on FF8 2000) # - 0: Low-res font # - 1: High-res font +# - 2: HD font (remastered only) #~~~~~~~~~~~~~~~~~~~~~~~~~~~ ff8_high_res_font = -1 +#[HD TEXTURES] +# Prevent the game from loading remastered HD PNG textures, this is a list of modules to disable. +# Available choices are: +# - field +# - battle +# - menu +# - cardgame +# - intro +# - world +#~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ff8_disable_remastered_hd_textures = [] + ## DEBUGGING OPTIONS - These options are mostly useful for developers or people reporting crashes. Please do enable them only when required. # Show the PSX SSIGPU VRAM window diff --git a/remastered-proxy/main.cpp b/remastered-proxy/main.cpp new file mode 100644 index 00000000..78966168 --- /dev/null +++ b/remastered-proxy/main.cpp @@ -0,0 +1,76 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2026 myst6re // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2020 Marcin Gomulak // +// Copyright (C) 2026 Julian Xhokaxhiu // +// Copyright (C) 2023 Cosmos // +// // +// 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 +#include +#include + +const char processes[][32]{ + "FF8.ffnx", + "FF8.exe" +}; +const int numProcesses = sizeof(processes) / sizeof(processes[0]); + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return TRUE; +} + +__declspec(dllexport) void __stdcall runGame() +{ + int process_to_start = -1; + + for (int i = 0; i < numProcesses; i++) + { + if (std::filesystem::exists(processes[i])) + { + process_to_start = i; + } + } + + if (process_to_start < 0) + { + MessageBoxA(NULL, "FF8.ffnx/FF8.exe not found", "Error", MB_ICONERROR | MB_OK); + } + + // Initialize the process start information + STARTUPINFOA si = STARTUPINFOA(); + PROCESS_INFORMATION pi = PROCESS_INFORMATION(); + si.cb = sizeof(si); + + // Start the process + if (!CreateProcessA(processes[process_to_start], NULL, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi)) + { + MessageBoxA(NULL, "Something went wrong while launching the game.", "Error", MB_ICONERROR | MB_OK); + return; + } + + // Wait for the process to finish + WaitForSingleObject(pi.hProcess, INFINITE); + + // Close process and thread handles + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return; +} diff --git a/src/achievement.cpp b/src/achievement.cpp index 5038988a..2d7b82b2 100644 --- a/src/achievement.cpp +++ b/src/achievement.cpp @@ -20,10 +20,6 @@ // GNU General Public License for more details. // /****************************************************************************/ -#include -#include -#include - #include #include #include @@ -151,7 +147,7 @@ void SteamManager::OnUserStatsReceived(UserStatsReceived_t *pCallback) { ach_trace("%s - received stats and achievements from Steam\n", __func__); this->isInitialized = true; - + // load stats (assume all stats to be integers) for (auto statName: this->stats) { int statValue; @@ -980,7 +976,7 @@ void SteamAchievementsFF8::unlockMagazineAddictAchievement(const savemap_ff8_ite } } ach_trace("%s - trying to unlock magazine addict achivement (magazines found: %d)\n", __func__, magazines_found.size()); - + if (magazines_found.size() >= MAGAZINES_TO_COLLECT) { this->steamManager->setAchievement(MAGAZINES_ADDICT); } diff --git a/src/achievement.h b/src/achievement.h index 17a81634..a87da55c 100644 --- a/src/achievement.h +++ b/src/achievement.h @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -33,6 +32,7 @@ #include "ff7.h" #include "ff8/save_data.h" +#include "steam.h" #define _ACH_ID(id) \ { \ diff --git a/src/audio/vgmstream/vgmstream.cpp b/src/audio/vgmstream/vgmstream.cpp index ccb27c78..4a62b846 100644 --- a/src/audio/vgmstream/vgmstream.cpp +++ b/src/audio/vgmstream/vgmstream.cpp @@ -21,6 +21,7 @@ #include "vgmstream.h" #include "../../utils.h" +#include "./zzzstreamfile.h" namespace SoLoud { @@ -116,16 +117,24 @@ namespace SoLoud result VGMStream::load(const char* aFilename, const char* ext) { mBaseSamplerate = 0; + STREAMFILE* zzz_stream = nullptr; - if (aFilename == 0) - return INVALID_PARAMETER; - - if (! fileExists(aFilename)) + if (strncmp(aFilename, "zzz://", 6) == 0) { + zzz_stream = open_ZZZ_STREAMFILE(aFilename + 6); + if (zzz_stream == nullptr) { + return FILE_NOT_FOUND; + } + } else if (! fileExists(aFilename)) { return FILE_NOT_FOUND; + } stop(); - if (ext && ext[0] != '\0') { + if (zzz_stream != nullptr) { + mStream = init_vgmstream_from_STREAMFILE(zzz_stream); + close_streamfile(zzz_stream); + } + else if (ext != nullptr && ext[0] != '\0') { mStream = init_vgmstream_with_extension(aFilename, ext); } else { diff --git a/src/audio/vgmstream/zzzstreamfile.cpp b/src/audio/vgmstream/zzzstreamfile.cpp new file mode 100644 index 00000000..21b8ff2d --- /dev/null +++ b/src/audio/vgmstream/zzzstreamfile.cpp @@ -0,0 +1,197 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 myst6re // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2022 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. // +/****************************************************************************/ +/************************************************************************************/ +// From vgmstream COPYING file: // +// Copyright (c) 2008-2019 Adam Gashlin, Fastelbja, Ronny Elfert, bnnm, // +// Christopher Snowhill, NicknineTheEagle, bxaimc, // +// Thealexbarney, CyberBotX, et al // +// // +// Portions Copyright (c) 2004-2008, Marko Kreen // +// Portions Copyright 2001-2007 jagarl / Kazunori Ueno // +// Portions Copyright (c) 1998, Justin Frankel/Nullsoft Inc. // +// Portions Copyright (C) 2006 Nullsoft, Inc. // +// Portions Copyright (c) 2005-2007 Paul Hsieh // +// Portions Public Domain originating with Sun Microsystems // +// // +// Permission to use, copy, modify, and distribute this software for any // +// purpose with or without fee is hereby granted, provided that the above // +// copyright notice and this permission notice appear in all copies. // +// // +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // +/************************************************************************************/ + +#include "zzzstreamfile.h" +#include "../../log.h" +#include "../../ff8/remaster.h" + +/* a STREAMFILE that operates via standard IO using a buffer */ +struct ZZZ_STREAMFILE { + STREAMFILE vt; /* callbacks */ + + Zzz::File* infile; /* actual FILE */ + offv_t offset; /* last read offset (info) */ + offv_t buf_offset; /* current buffer data start */ + uint8_t* buf; /* data buffer */ + size_t buf_size; /* max buffer size */ + size_t valid_size; /* current buffer size */ +}; + +size_t zzz_read(ZZZ_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) +{ + if (trace_all) ffnx_trace("%s: %s\n", __func__, sf->infile->fileName()); + size_t read_total = 0; + + if (dst == nullptr || length <= 0 || offset < 0) + { + return read_total; + } + + /* is the part of the requested length in the buffer? */ + if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) + { + size_t buf_limit; + int buf_into = int(offset - sf->buf_offset); + + buf_limit = sf->valid_size - buf_into; + if (buf_limit > length) { + buf_limit = length; + } + + memcpy(dst, sf->buf + buf_into, buf_limit); + read_total += buf_limit; + length -= buf_limit; + offset += buf_limit; + dst += buf_limit; + } + + /* read the rest of the requested length */ + while (length > 0) + { + size_t length_to_read; + + /* ignore requests at EOF */ + if (offset >= sf->infile->size() || sf->infile->seek(offset, bx::Whence::Begin) < 0) + { + break; + } + + /* fill the buffer (offset now is beyond buf_offset) */ + sf->buf_offset = offset; + sf->valid_size = sf->infile->read((char *)sf->buf, sf->buf_size); + + /* decide how much must be read this time */ + length_to_read = length > sf->buf_size ? sf->buf_size : length; + + /* give up on partial reads (EOF) */ + if (sf->valid_size < length_to_read) { + memcpy(dst, sf->buf, sf->valid_size); + offset += sf->valid_size; + read_total += sf->valid_size; + break; + } + + /* use the new buffer */ + memcpy(dst, sf->buf, length_to_read); + offset += length_to_read; + read_total += length_to_read; + length -= length_to_read; + dst += length_to_read; + } + + sf->offset = offset; /* last fread offset */ + return read_total; +} + +size_t zzz_get_size(ZZZ_STREAMFILE* sf) +{ + return sf->infile->size(); +} + +offv_t zzz_get_offset(ZZZ_STREAMFILE* sf) +{ + return sf->infile->relativePos(); +} + +void zzz_get_name(ZZZ_STREAMFILE* sf, char* name, size_t name_size) +{ + int copy_size = sf->infile->fileNameSize() + 1; + if (copy_size > name_size) + { + copy_size = name_size; + } + + memcpy(name, sf->infile->fileName(), copy_size); + name[copy_size - 1] = '\0'; + if (trace_all) ffnx_trace("%s: %s %d %d %s\n", __func__, name, copy_size, name_size, sf->infile->fileName()); +} + +STREAMFILE* zzz_open(ZZZ_STREAMFILE* sf, const char* const filename, size_t buf_size) +{ + if (trace_all || trace_files) ffnx_trace("%s: %s\n", __func__, filename); + return open_ZZZ_STREAMFILE(filename, buf_size); +} + +void zzz_close(ZZZ_STREAMFILE* sf) +{ + if (trace_all || trace_files) ffnx_trace("%s: %s\n", __func__, sf->infile->fileName()); + Zzz::closeFile(sf->infile); + delete[] sf->buf; + delete sf; +} + +STREAMFILE* open_ZZZ_STREAMFILE(const char* const filename, size_t buf_size) +{ + if (trace_all || trace_files) ffnx_trace("%s: %s\n", __func__, filename); + if (filename == nullptr) + { + return nullptr; + } + + Zzz::File* zzz_file = g_FF8ZzzArchiveOther.openFile(filename); + + if (zzz_file == nullptr) { + return nullptr; + } + + uint8_t* buf = new uint8_t[buf_size]; + memset(buf, 0, buf_size); + + ZZZ_STREAMFILE* this_sf = new ZZZ_STREAMFILE(); + + this_sf->vt.read = (size_t (*)(STREAMFILE*, uint8_t*, offv_t, size_t))zzz_read; + this_sf->vt.get_size = (size_t (*)(STREAMFILE*))zzz_get_size; + this_sf->vt.get_offset = (offv_t (*)(STREAMFILE*))zzz_get_offset; + this_sf->vt.get_name = (void (*)(STREAMFILE*, char*, size_t))zzz_get_name; + this_sf->vt.open = (STREAMFILE* (*)(STREAMFILE*, const char* const, size_t))zzz_open; + this_sf->vt.close = (void (*)(STREAMFILE*))zzz_close; + + this_sf->infile = zzz_file; + this_sf->buf_size = buf_size; + this_sf->buf = buf; + + return &this_sf->vt; +} diff --git a/src/audio/vgmstream/zzzstreamfile.h b/src/audio/vgmstream/zzzstreamfile.h new file mode 100644 index 00000000..593f3249 --- /dev/null +++ b/src/audio/vgmstream/zzzstreamfile.h @@ -0,0 +1,35 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 myst6re // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2022 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 + +#if defined(__cplusplus) +extern "C" { +#endif + +#include + +#if defined(__cplusplus) +} +#endif + +#include "../../ff8/zzz_archive.h" + +STREAMFILE* open_ZZZ_STREAMFILE(const char* const filename, size_t buf_size = STREAMFILE_DEFAULT_BUFFER_SIZE); diff --git a/src/cfg.cpp b/src/cfg.cpp index 5f02d28f..228138af 100644 --- a/src/cfg.cpp +++ b/src/cfg.cpp @@ -158,6 +158,7 @@ long ffmpeg_video_volume; bool ff7_advanced_blinking; long display_index; long ff8_high_res_font; +std::vector ff8_disable_remastered_hd_textures; std::vector get_string_or_array_of_strings(const toml::node_view &node) { @@ -325,6 +326,7 @@ void read_cfg() ff7_advanced_blinking = config["ff7_advanced_blinking"].value_or(false); display_index = config["display_index"].value_or(-1); ff8_high_res_font = config["ff8_high_res_font"].value_or(-1); + ff8_disable_remastered_hd_textures = get_string_or_array_of_strings(config["ff8_disable_remastered_hd_textures"]); // Windows x or y size can't be less then 0 if (window_size_x < 0) window_size_x = 0; @@ -526,3 +528,8 @@ void read_cfg() // DISPLAY INDEX if (display_index < 1) display_index = -1; } + +bool is_remastered_hd_textures_disabled(const std::string &module) +{ + return std::find(ff8_disable_remastered_hd_textures.begin(), ff8_disable_remastered_hd_textures.end(), module) != ff8_disable_remastered_hd_textures.end(); +} diff --git a/src/cfg.h b/src/cfg.h index b048831b..02496a15 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -170,5 +170,7 @@ extern long ffmpeg_video_volume; extern bool ff7_advanced_blinking; extern long display_index; extern long ff8_high_res_font; +extern std::vector ff8_disable_remastered_hd_textures; void read_cfg(); +bool is_remastered_hd_textures_disabled(const std::string &module); diff --git a/src/common.cpp b/src/common.cpp index b7ff55d6..8e226eea 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -66,6 +65,7 @@ #include "game_cfg.h" #include "exe_data.h" #include "utils.h" +#include "steam.h" #include "ff7/defs.h" #include "ff7/widescreen.h" @@ -79,6 +79,7 @@ #include "ff8/uv_patch.h" #include "ff8/ambient.h" #include "ff8/file.h" +#include "ff8/remaster.h" #include "wine.h" @@ -132,6 +133,12 @@ uint32_t ff7_steam_rerelease_edition = false; // global FF7/FF8 flag, check if using the steam stock launcher uint32_t steam_stock_launcher = false; +// global FF8 flag, check if is remastered edition +uint32_t remastered_edition = false; + +// globale FF7/FF8 flag, check if it is GOG edition +uint32_t gog_edition = false; + // global FF7 flag, check if is eStore edition uint32_t estore_edition = false; @@ -813,9 +820,10 @@ int common_create_window(HINSTANCE hInstance, struct game_obj* game_object) // Init Steam API if(enable_steam_achievements) { - int app_id = ff8 ? FF8_APPID : (ff7_steam_rerelease_edition ? FF7_RERELEASE_APPID : FF7_APPID); + int app_id = ff8 ? (remastered_edition ? FF8_REMASTERED_APPID : FF8_APPID) : (ff7_steam_rerelease_edition ? FF7_RERELEASE_APPID : FF7_APPID); // generate automatically steam_appid.txt - if(!steam_edition){ + if(!steam_edition && !remastered_edition) + { std::ofstream steam_appid_file("steam_appid.txt"); steam_appid_file << app_id; steam_appid_file.close(); @@ -878,8 +886,8 @@ int common_create_window(HINSTANCE hInstance, struct game_obj* game_object) } else { - window_size_x = game_width; - window_size_y = game_height; + window_size_x = remastered_edition ? 1024 : game_width; + window_size_y = remastered_edition ? 768 : game_height; } } else @@ -1033,6 +1041,9 @@ int common_create_window(HINSTANCE hInstance, struct game_obj* game_object) vram_init(); if (ff8_fix_uv_coords_precision) uv_patch_init(); vibration_init(); + if (remastered_edition) { + ff8_remaster_init(); + } if (widescreen_enabled) { *ff8_externals.current_viewport_x_dword_1A7764C = wide_viewport_x; @@ -1656,6 +1667,31 @@ uint32_t load_external_texture(void* image_data, uint32_t dataSize, struct textu if(!_strnicmp(VREF(tex_header, file.pc_name), "flevel/hand_1", strlen("flevel/hand_1") - 1)) gl_set->force_filter = true; } + else if (*(VREF(tex_header, file.pc_name) + 512) != '\0') // has secondary hd path + { + if(trace_all || trace_loaders) ffnx_trace("texture file name (alternative): %s\n", VREF(tex_header, file.pc_name) + 512); + + if (texture == 0) + { + texture = load_texture(image_data, dataSize, VREF(tex_header, file.pc_name) + 512, VREF(tex_header, palettes) > 1 ? VREF(tex_header, palette_index) | 0xC0000000 : -1, VREFP(texture_set, ogl.width), VREFP(texture_set, ogl.height), gl_set); + } + + // Retry inside zzz archive for the remastered + if (remastered_edition && texture == 0) + { + char filename[MAX_PATH] = {}; + if (VREF(tex_header, palettes) > 1 + && _strnicmp(VREF(tex_header, file.pc_name) + 512, "cards\\", sizeof("cards\\") - 1) != 0 + && _strnicmp(VREF(tex_header, file.pc_name) + 512, "field.fs\\field_hd_new\\wmset_014_0", sizeof("field.fs\\field_hd_new\\wmset_014_0") - 1) != 0) { + _snprintf(filename, sizeof(filename), "zzz://textures\\%s\\%d.png", VREF(tex_header, file.pc_name) + 512, VREF(tex_header, palette_index)); + } else if (VREF(tex_header, palette_index) == 0 && _strnicmp(VREF(tex_header, file.pc_name) + 512, "cards\\text_1", sizeof("cards\\text_1") - 1) == 0) { + _snprintf(filename, sizeof(filename), "zzz://textures\\cards\\text_1_mask%s.png", VREF(tex_header, file.pc_name) + 512 + sizeof("cards\\text_1") - 1); + } else { + _snprintf(filename, sizeof(filename), "zzz://textures\\%s.png", VREF(tex_header, file.pc_name) + 512); + } + texture = newRenderer.createTextureLibPng(filename, VREFP(texture_set, ogl.width), VREFP(texture_set, ogl.height)); + } + } } if(ff8 && texture == 0) @@ -2826,13 +2862,12 @@ uint32_t get_version() return 0; } -void get_data_lang_path(PCHAR buffer) +void concat_lang_str(PCHAR buffer) { - strcpy(buffer, ff8 ? ff8_externals.app_path : basedir); - PathAppendA(buffer, R"(data\lang-)"); switch (version) { case VERSION_FF7_102_US: + case VERSION_FF8_12_US: case VERSION_FF8_12_US_NV: case VERSION_FF8_12_US_EIDOS_NV: if (ff7_japanese_edition) @@ -2841,26 +2876,40 @@ void get_data_lang_path(PCHAR buffer) strcat(buffer, "en"); break; case VERSION_FF7_102_FR: + case VERSION_FF8_12_FR: case VERSION_FF8_12_FR_NV: strcat(buffer, "fr"); break; case VERSION_FF7_102_DE: + case VERSION_FF8_12_DE: case VERSION_FF8_12_DE_NV: strcat(buffer, "de"); break; case VERSION_FF7_102_SP: + case VERSION_FF8_12_SP: case VERSION_FF8_12_SP_NV: strcat(buffer, "es"); break; + case VERSION_FF8_12_IT: case VERSION_FF8_12_IT_NV: strcat(buffer, "it"); break; case VERSION_FF8_12_JP: + case VERSION_FF8_12_JP_NV: strcat(buffer, "jp"); break; } } +void get_data_lang_path(PCHAR buffer, bool absolute) +{ + if (absolute) { + strcpy(buffer, ff8 ? ff8_externals.app_path : basedir); + } + PathAppendA(buffer, R"(data\lang-)"); + concat_lang_str(buffer); +} + void get_userdata_path(PCHAR buffer, size_t bufSize, bool isSavegameFile) { PWSTR outPath = NULL; @@ -2873,8 +2922,15 @@ void get_userdata_path(PCHAR buffer, size_t bufSize, bool isSavegameFile) CoTaskMemFree(outPath); - if (ff8) - PathAppendA(buffer, R"(Square Enix\FINAL FANTASY VIII Steam)"); + if (ff8) { + if (remastered_edition) + { + PathAppendA(buffer, R"(My Games\FINAL FANTASY VIII Remastered)"); + PathAppendA(buffer, gog_edition ? R"(GOG)" : R"(Steam)"); + } + else + PathAppendA(buffer, R"(Square Enix\FINAL FANTASY VIII Steam)"); + } else PathAppendA(buffer, R"(Square Enix\FINAL FANTASY VII Steam)"); @@ -2885,6 +2941,34 @@ void get_userdata_path(PCHAR buffer, size_t bufSize, bool isSavegameFile) // Directly use the given userdata PathAppendA(buffer, steam_game_userdata.c_str()); } + else if (remastered_edition) + { + // Find first directory with only numbers + for (const auto &dirEntry: std::filesystem::directory_iterator(buffer)) + { + if (dirEntry.is_directory()) + { + bool nonDigitFound = false; + const auto &filename = dirEntry.path().filename().string(); + for (const char &c: filename) + { + if (!std::isdigit(c)) + { + nonDigitFound = true; + break; + } + } + + if (!nonDigitFound) + { + PathAppendA(buffer, filename.c_str()); + break; + } + } + } + + PathAppendA(buffer, R"(game_data\user\saves)"); + } else { // Search for the first "user_" match in the game path @@ -3033,15 +3117,6 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) return FALSE; } - bool is_genuine_steam_api = isFileSigned("steam_api.dll"); - if (!is_genuine_steam_api) is_genuine_steam_api = sha1_file("steam_api.dll") == "03bd9f3e352553a0af41f5fe006f6249a168c243"; - if (!is_genuine_steam_api) - { - ffnx_unexpected("Invalid steam_api.dll detected. Please ensure your FFNx installation is not corrupted or tampered by unauthorized software.\n"); - MessageBoxA(NULL, "Invalid steam_api.dll detected. Please ensure your FFNx installation is not corrupted or tampered by unauthorized software.", "Error", MB_ICONERROR | MB_OK); - return FALSE; - } - read_cfg(); // Did user choose to enable Widescreen? @@ -3078,7 +3153,11 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) ffnx_trace("Detected Steam Rerelease edition.\n"); } else if(fileExists("../../goggame-1698970154.info")) + { + gog_edition = true; + ffnx_trace("Detected GOG edition.\n"); + } else ffnx_trace("Detected Windows Store edition.\n"); @@ -3204,9 +3283,34 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) replace_function(common_externals.create_window, common_create_window); replace_function(ff8_externals.manage_time_engine_sub_569971, ff8_manage_time_engine); - game_cfg_init(); + if (fileExists("FFVIII.exe")) + { + ffnx_trace("Detected Remastered edition.\n"); - if (strstr(dllName, "af3dn.p") != NULL) + remastered_edition = true; + + if(fileExists("goggame-1086370078.info")) + { + gog_edition = true; + + ffnx_trace("Detected GOG edition.\n"); + } + else + { + ffnx_trace("Detected Steam edition.\n"); + } + + use_external_music = true; + if (external_music_path.empty()) external_music_path = "data/music/dmusic/ogg"; + // Steam edition contains movies unpacked + enable_ffmpeg_videos = true; + ff8_external_music_force_original_filenames = true; // FIXME: allow old mods? + + patch_code_byte(uint32_t(ff8_externals.set_game_paths) + 0x1F0, DRIVE_NO_ROOT_DIR); + memcpy_code(uint32_t(ff8_externals.archive_path_prefix_field), "\\ff8\\data\\x\\field\\", sizeof("\\ff8\\data\\x\\field\\")); + memcpy_code(uint32_t(ff8_externals.archive_path_prefix_world), "\\ff8\\data\\x\\world\\", sizeof("\\ff8\\data\\x\\world\\")); + } + else if (strstr(dllName, "af3dn.p") != NULL) { ffnx_trace("Detected Steam 2013 edition.\n"); @@ -3254,6 +3358,8 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) if (ffmpeg_video_volume < 0) ffmpeg_video_volume = 100; } + game_cfg_init(); + // Init metadata patcher if (steam_edition) metadataPatcher.init(); @@ -3433,97 +3539,16 @@ __declspec(dllexport) LSTATUS __stdcall dotemuRegQueryValueExA(HKEY hKey, LPCSTR __declspec(dllexport) HANDLE __stdcall dotemuCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { - if (ff8_fs_last_fopen_is_redirected()) - { - return CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - } - - HANDLE ret = INVALID_HANDLE_VALUE; - - if (strstr(lpFileName, "CD:") != NULL) - { - CHAR newPath[MAX_PATH]{ 0 }; - uint8_t requiredDisk = (*ff8_externals.savemap_field)->curr_disk; - CHAR diskAsChar[2]; - - itoa(requiredDisk, diskAsChar, 10); - - // Search for the last '\' character and get a pointer to the next char - const char* pos = strrchr(lpFileName, 92) + 1; - - if (strstr(lpFileName, "DISK1") != NULL || strstr(lpFileName, "DISK2") != NULL || strstr(lpFileName, "DISK3") != NULL || strstr(lpFileName, "DISK4") != NULL) - { - PathAppendA(newPath, ff8_externals.app_path); - PathAppendA(newPath, R"(data\disk)"); - PathAppendA(newPath, pos); - - if (strstr(lpFileName, diskAsChar) != NULL) - { - ret = CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - } - } - } - else if (strstr(lpFileName, "app.log") || strstr(lpFileName, "ff8input.cfg")) - { - CHAR newPath[MAX_PATH]{ 0 }; - - // Search for the last '\' character and get a pointer to the next char - const char* pos = strrchr(lpFileName, 92) + 1; - - get_userdata_path(newPath, sizeof(newPath), false); - PathAppendA(newPath, JP_VERSION ? "ff8input_jp.cfg" : pos); - - ret = CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - } - else if (strstr(lpFileName, "temp.fi") || strstr(lpFileName, "temp.fl") || strstr(lpFileName, "temp.fs") || strstr(lpFileName, "temp_evn.") || strstr(lpFileName, "temp_odd.")) + if (!ff8_fs_last_fopen_is_redirected()) { - CHAR newPath[MAX_PATH]{ 0 }; - - // Search for the last '\' character and get a pointer to the next char - const char* pos = strrchr(lpFileName, 92) + 1; + char newPath[MAX_PATH] = {}; - get_userdata_path(newPath, sizeof(newPath), false); - PathAppendA(newPath, pos); - - ret = CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - } - else if (strstr(lpFileName, ".fi") != NULL || strstr(lpFileName, ".fl") != NULL || strstr(lpFileName, ".fs") != NULL) - { - CHAR newPath[MAX_PATH]{ 0 }; - - // Search for the last '\' character and get a pointer to the next char - const char* pos = strrchr(lpFileName, 92) + 1; - - get_data_lang_path(newPath); - PathAppendA(newPath, pos); - - ret = CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - } - else if (StrStrIA(lpFileName, R"(SAVE\)") != NULL) // SAVE\SLOTX\saveN or save\chocorpg - { - CHAR newPath[MAX_PATH]{ 0 }; - CHAR saveFileName[50]{ 0 }; - - // Search for the next character pointer after "SAVE\" - const char* pos = StrStrIA(lpFileName, R"(SAVE\)") + 5; - strcpy(saveFileName, pos); - _strlwr(saveFileName); - char* posSeparator = strstr(saveFileName, R"(\)"); - if (posSeparator != NULL) - { - *posSeparator = '_'; + if (ff8_steam_redirection(lpFileName, newPath)) { + return CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } - strcat(saveFileName, R"(.ff8)"); - - get_userdata_path(newPath, sizeof(newPath), true); - PathAppendA(newPath, saveFileName); - - ret = CreateFileA(newPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } - else - ret = CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - return ret; + return CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } __declspec(dllexport) UINT __stdcall dotemuGetDriveTypeA(LPCSTR lpRootPathName) diff --git a/src/common.h b/src/common.h index 7025884c..bfc442ff 100644 --- a/src/common.h +++ b/src/common.h @@ -53,6 +53,7 @@ #define FF7_APPID 39140 #define FF7_RERELEASE_APPID 3837340 #define FF8_APPID 39150 +#define FF8_REMASTERED_APPID 1026680 #define NV_VERSION (!(version & 1)) #define JP_VERSION (version == VERSION_FF8_12_JP || version == VERSION_FF8_12_JP_NV) @@ -356,7 +357,8 @@ void internal_set_renderstate(uint32_t state, uint32_t option, struct game_obj * uint32_t create_framebuffer_texture(struct texture_set *texture_set, struct tex_header *tex_header); void blit_framebuffer_texture(struct texture_set *texture_set, struct tex_header *tex_header); -void get_data_lang_path(PCHAR buffer); +void get_data_lang_path(PCHAR buffer, bool absolute = true); +void concat_lang_str(PCHAR buffer); void get_userdata_path(PCHAR buffer, size_t bufSize, bool isSavegameFile); #if defined(__cplusplus) diff --git a/src/ff8.h b/src/ff8.h index 801ddb8a..1ae435bc 100644 --- a/src/ff8.h +++ b/src/ff8.h @@ -1197,6 +1197,16 @@ struct ff8_externals uint32_t (*ctrl_keyboard_actions)(); uint32_t get_key_state; byte **keyboard_state; + uint32_t sub_533C30; + uint32_t model_vertices_scale_sub_45FE10; + uint16_t *camera_zoom_dword_1CA92E4; + int16_t *word_1CA92DE; + int *dword_1CA8A50; + int *dword_1CA92F8; + int16_t *dword_1CA8A10; + int *dword_1CA9290; + float *flt_1CA9234; + int *dword_1CA8A30; uint32_t sub_4789A0; char (*sub_47CA90)(); uint32_t field_update_seed_level_52B140; @@ -1297,6 +1307,7 @@ struct ff8_externals uint32_t read_field_data; uint32_t upload_mim_file; uint32_t upload_pmp_file; + uint32_t field_filename_concat_extension; char *field_filename; int (*field_scripts_init)(int, int, int, int); uint8_t *field_state_background_count; @@ -1449,6 +1460,7 @@ struct ff8_externals uint32_t opcode_movie; uint32_t opcode_moviesync; uint32_t opcode_spuready; + uint32_t opcode_show; uint32_t opcode_movieready; uint32_t opcode_setvibrate; uint32_t opcode_musicload; @@ -1512,6 +1524,10 @@ struct ff8_externals uint32_t sub_464DB0; uint32_t sub_4649A0; char *archive_path_prefix; + char *archive_path_prefix_menu; + char *archive_path_prefix_battle; + char *archive_path_prefix_field; + char *archive_path_prefix_world; int(*fs_archive_search_filename)(const char *, ff8_file_fi_infos *, const ff8_file_container *); int(*ff8_fs_archive_search_filename2)(const char *, ff8_file_fi_infos *, const ff8_file_container *); char *(*fs_archive_get_fl_filepath)(int, const ff8_file_fl *); @@ -1519,6 +1535,23 @@ struct ff8_externals int(*_sopen)(const char*, int, int, ...); uint32_t fopen; FILE *(*_fsopen)(const char*, const char*, int); + int (*_lseek)(int,long,int); + int (*_lseek_lk)(int,long,int); + unsigned int *_io_fd_number; + int *_io_known_fds; + void (*_lock_fhandle)(int); + int (*_unlock_fhandle)(int); + int* (*_errno)(); + unsigned long* (*__doserrno)(); + int (*_read)(int,void*,unsigned int); + unsigned int (*_read_lk)(int,LPVOID,DWORD); + uint32_t open_and_write_to_archive; + uint32_t write_to_archive; + int (*_write)(int,void*,unsigned int); + int (*_write_lk)(int,LPVOID,DWORD); + int (*_close)(int); + int (*_close_lk)(int); + __int32 (*_filelength)(int); uint32_t input_init; uint32_t ff8input_cfg_read; uint32_t ff8input_cfg_reset; @@ -1610,6 +1643,7 @@ struct ff8_externals uint32_t sfx_is_playing; uint32_t sfx_set_panning; uint16_t *sfx_sound_count; + uint32_t sfx_initialize_audio_data; ff8_audio_fmt **sfx_audio_fmt; uint32_t manage_time_engine_sub_569971; int (*enable_rdtsc_sub_40AA00)(int enable); @@ -1675,6 +1709,9 @@ struct ff8_externals int* battle_magic_id; uint32_t sub_571870; DWORD* func_off_battle_effect_textures_50AF93; + uint32_t get_battle_effect_buffer_sub_571B50; + uint32_t get_battle_effect_buffer_size_sub_571B60; + uint32_t init_battle_effect_buffer_sub_571B80; uint32_t sub_6C3640; uint32_t sub_6C3760; uint8_t **vibrate_data_summon_quezacotl; @@ -1747,6 +1784,7 @@ struct ff8_externals int32_t *current_viewport_y_dword_1A77648; int32_t *current_viewport_width_dword_1A77654; int32_t *current_viewport_height_dword_1A77650; + void **dword_1DCB340; uint32_t set_render_to_vram_current_screen_flag_before_battle; uint32_t sub_472B30; uint32_t sub_530810; diff --git a/src/ff8/field/chara_one.cpp b/src/ff8/field/chara_one.cpp index 675027cd..cf0a3387 100644 --- a/src/ff8/field/chara_one.cpp +++ b/src/ff8/field/chara_one.cpp @@ -58,6 +58,7 @@ std::unordered_map ff8_chara_one_parse_models(const uin } CharaOneModel model = CharaOneModel(); + model.modelId = i; if (flag >> 24 != 0xd0) { // NPCs (not main characters) uint32_t tim_offset; diff --git a/src/ff8/field/chara_one.h b/src/ff8/field/chara_one.h index 34d3b2a3..4bc679ec 100644 --- a/src/ff8/field/chara_one.h +++ b/src/ff8/field/chara_one.h @@ -30,6 +30,7 @@ struct CharaOneModel { char name[6]; bool isMch; std::vector texturesData; + uint32_t modelId; }; std::unordered_map ff8_chara_one_parse_models(const uint8_t *chara_one_data, size_t size); diff --git a/src/ff8/file.cpp b/src/ff8/file.cpp index f24b5d38..c2ec05ad 100644 --- a/src/ff8/file.cpp +++ b/src/ff8/file.cpp @@ -25,16 +25,22 @@ #include "../ff8.h" #include "../log.h" #include "../redirect.h" +#include "../patch.h" +#include "remaster.h" #include #include +#include #include +#include +#include "Shlwapi.h" char next_direct_file[MAX_PATH] = ""; bool last_fopen_is_redirected = false; uint32_t last_compression_type = 0; size_t last_compressed_size = 0; size_t last_uncompressed_size = 0; +std::map openedZzzFiles; size_t get_fl_prefix_size(bool with_lang = true) { @@ -126,13 +132,34 @@ ff8_file_container *ff8_fs_archive_open_temp(char *fl_path, char *fs_path, char return ff8_externals.archive_open(fl_path, fs_path, fi_path); } +int ff8_remastered_open_from_zzz_archives(const char *fileName) +{ + if (trace_all || trace_files) ffnx_trace("%s: fileName=%s\n", __func__, fileName); + + Zzz *archive = &g_FF8ZzzArchiveMain; + + if (strstr(fileName, "data\\sound\\") != nullptr + || strstr(fileName, "data\\music\\") != nullptr) { + archive = &g_FF8ZzzArchiveOther; + } + + Zzz::File *file = archive->openFile(fileName); + if (file != nullptr) { + openedZzzFiles[file->fd()] = file; + + return file->fd(); + } + + return -1; +} + int ff8_fs_archive_search_filename2(const char *fullpath, ff8_file_fi_infos *fi_infos_for_the_path, const ff8_file_container *file_container) { if (trace_all || trace_files) ffnx_trace("%s: Looking in archive for %s\n", __func__, fullpath); int ret = ff8_externals.ff8_fs_archive_search_filename2(fullpath, fi_infos_for_the_path, file_container); - if (ret != 1 && file_container != nullptr) + if (ret != 1 && file_container != nullptr && !remastered_edition) { // Lookup without the language in the path size_t prefix_size = get_fl_prefix_size(); @@ -153,6 +180,28 @@ int ff8_fs_archive_search_filename2(const char *fullpath, ff8_file_fi_infos *fi_ } } } + else if (ret != 1 && remastered_edition) + { + int fullpath_len = strlen(fullpath); + if (fullpath_len > 4) { + char extension[5] = {}; + strncpy(extension, fullpath + fullpath_len - 4, 4); + + char *modifiablePath = const_cast(fullpath); + modifiablePath[fullpath_len - 4] = '_'; + modifiablePath[fullpath_len - 3] = '\0'; + concat_lang_str(modifiablePath); + strcat(modifiablePath, extension); + + ffnx_error("%s: retry with %s...\n", __func__, modifiablePath); + + ret = ff8_externals.ff8_fs_archive_search_filename2(modifiablePath, fi_infos_for_the_path, file_container); + } + + if (ret != 1) { + ffnx_error("%s: file not found: %s\n", __func__, fullpath); + } + } return ret; } @@ -261,6 +310,22 @@ bool ff8_attempt_redirection(const char *in, char *out, size_t size) return false; } +void ff8_fs_archive_field_concat_extension(char *fileName, char *extension) +{ + // Remastered edition only + if (strstr(extension, ".msd") != NULL || strstr(extension, ".jsm") != NULL + || (JP_VERSION && strstr(extension, ".inf") != NULL)) + { + strcat(fileName, "_"); + concat_lang_str(fileName); + strcat(fileName, extension); + } + else + { + strcat(fileName, extension); + } +} + int ff8_open(const char *fileName, int oflag, ...) { va_list va; @@ -283,6 +348,25 @@ int ff8_open(const char *fileName, int oflag, ...) } char _filename[MAX_PATH]{ 0 }; + + if (remastered_edition) + { + bool isZzzFile = false; + bool is_redirected = ff8_steam_redirection(fileName, _filename, &isZzzFile); + + if (oflag == (_O_BINARY | _O_RDONLY) && isZzzFile) { + int ret = ff8_remastered_open_from_zzz_archives(is_redirected ? _filename : fileName); + + if (ret != -1) { + return ret; + } + + if (trace_all || trace_files) ffnx_info("Fallback to Steam path mode %s\n", _filename); + } + + return ff8_externals._sopen(is_redirected ? _filename : fileName, oflag, shflag, pmode); + } + bool is_redirected = ff8_attempt_redirection(fileName, _filename, sizeof(_filename)); last_fopen_is_redirected = is_redirected; @@ -294,6 +378,155 @@ int ff8_open(const char *fileName, int oflag, ...) return ret; } +int ff8_read(int fd, void *buffer, unsigned int bufferSize) +{ + if (trace_all || trace_files) ffnx_info("%s: fd=%X bufferSize=%d\n", __func__, fd, bufferSize); + + if (remastered_edition && openedZzzFiles.contains(fd)) + { + return openedZzzFiles.at(fd)->read(buffer, bufferSize); + } + + if (fd < *ff8_externals._io_fd_number && (*(uint8_t *)(ff8_externals._io_known_fds[fd >> 5] + 36 * (fd & 0x1F) + 4) & 1)) + { + ff8_externals._lock_fhandle(fd); + int ret = ff8_externals._read_lk(fd, buffer, bufferSize); + ff8_externals._unlock_fhandle(fd); + + return ret; + } + + *(ff8_externals._errno()) = EBADF; + *(ff8_externals.__doserrno()) = 0; + + return -1; +} + +int ff8_write(int fd, void *buffer, unsigned int bufferSize) +{ + if (trace_all || trace_files) ffnx_info("%s: fd=%X bufferSize=%d\n", __func__, fd, bufferSize); + + if (remastered_edition && openedZzzFiles.contains(fd)) + { + ffnx_error("%s: Trying to write in a ZZZ archive is forbidden\n"); + + return -1; + } + + if (fd < *ff8_externals._io_fd_number && (*(uint8_t *)(ff8_externals._io_known_fds[fd >> 5] + 36 * (fd & 0x1F) + 4) & 1)) + { + ff8_externals._lock_fhandle(fd); + int ret = ff8_externals._write_lk(fd, buffer, bufferSize); + ff8_externals._unlock_fhandle(fd); + + return ret; + } + + *(ff8_externals._errno()) = EBADF; + *(ff8_externals.__doserrno()) = 0; + + return -1; +} + +__int32 ff8_lseek(int fd, __int32 offset, int whence) +{ + if (trace_all || trace_files) ffnx_info("%s: fd=%X, offset=%d, whence=%d\n", __func__, fd, offset, whence); + + if (remastered_edition && openedZzzFiles.contains(fd)) { + Zzz::File *file = openedZzzFiles.at(fd); + uint32_t pos = offset; + bx::Whence::Enum zzzWhence = bx::Whence::Begin; + + if (whence == SEEK_END) { + zzzWhence = bx::Whence::End; + } else if (whence == SEEK_CUR) { + zzzWhence = bx::Whence::Current; + } else if (whence != SEEK_SET) { + ffnx_error("%s: seek type not supported: %d\n", __func__, whence); + + return -1; + } + + return file->seek(pos, zzzWhence); + } + + // Original implementation + if (fd < *ff8_externals._io_fd_number && (*(uint8_t *)(ff8_externals._io_known_fds[fd >> 5] + 36 * (fd & 0x1F) + 4) & 1)) + { + ff8_externals._lock_fhandle(fd); + int ret = ff8_externals._lseek_lk(fd, offset, whence); + ff8_externals._unlock_fhandle(fd); + + return ret; + } + + *(ff8_externals._errno()) = EBADF; + *(ff8_externals.__doserrno()) = 0; + + return -1; +} + +__int32 ff8_filelength(int fd) +{ + if (trace_all) ffnx_info("%s: fd=%X\n", __func__, fd); + + if (remastered_edition && openedZzzFiles.contains(fd)) { + return openedZzzFiles.at(fd)->size(); + } + + // Original implementation + if (fd < *ff8_externals._io_fd_number && (*(uint8_t *)(ff8_externals._io_known_fds[fd >> 5] + 36 * (fd & 0x1F) + 4) & 1)) + { + ff8_externals._lock_fhandle(fd); + int currentPos = ff8_externals._lseek_lk(fd, 0, SEEK_CUR); + int fileSize = -1; + if (currentPos != -1) + { + fileSize = ff8_externals._lseek_lk(fd, 0, SEEK_END); + + if (fileSize != currentPos) + { + ff8_externals._lseek_lk(fd, currentPos, SEEK_SET); + } + } + ff8_externals._unlock_fhandle(fd); + + return fileSize; + } + + *(ff8_externals._errno()) = EBADF; + *(ff8_externals.__doserrno()) = 0; + + return -1; +} + +int ff8_close(int fd) +{ + if (trace_all || trace_files) ffnx_info("%s: fd=%X\n", __func__, fd); + + if (remastered_edition && openedZzzFiles.contains(fd)) { + Zzz::closeFile(openedZzzFiles.at(fd)); + openedZzzFiles.erase(fd); + + return 0; + } + + // Original implementation + if (fd < *ff8_externals._io_fd_number && (*(uint8_t *)(ff8_externals._io_known_fds[fd >> 5] + 36 * (fd & 0x1F) + 4) & 1)) + { + ff8_externals._lock_fhandle(fd); + int ret = ff8_externals._close_lk(fd); + ff8_externals._unlock_fhandle(fd); + + return ret; + } + + *(ff8_externals._errno()) = EBADF; + *(ff8_externals.__doserrno()) = 0; + + return -1; +} + FILE *ff8_fopen(const char *fileName, const char *mode) { if (trace_all || trace_files) ffnx_trace("%s: %s mode=%s\n", __func__, fileName, mode); @@ -312,6 +545,13 @@ FILE *ff8_fopen(const char *fileName, const char *mode) } char _filename[MAX_PATH]{ 0 }; + + if (remastered_edition) + { + bool is_redirected = ff8_steam_redirection(fileName, _filename); + return ff8_externals._fsopen(is_redirected ? _filename : fileName, mode, shflag); + } + bool is_redirected = ff8_attempt_redirection(fileName, _filename, sizeof(_filename)); last_fopen_is_redirected = is_redirected; @@ -402,15 +642,43 @@ ff8_file *ff8_open_file(ff8_file_context *infos, const char *fs_path) } else { + file->fd = -1; char _filename[256]{ 0 }; - bool is_redirected = ff8_attempt_redirection(fullpath, _filename, sizeof(_filename)); - last_fopen_is_redirected = is_redirected; + if (remastered_edition) + { + bool isZzzFile = false; + bool is_redirected = ff8_steam_redirection(fullpath, _filename, &isZzzFile); + isZzzFile = isZzzFile && oflag == (_O_BINARY | _O_RDONLY); - // We need to use the external _open, and not the official one - file->fd = ff8_externals._sopen(is_redirected ? _filename : fullpath, oflag, shflag, pmode); + if (isZzzFile) { + file->fd = ff8_remastered_open_from_zzz_archives(is_redirected ? _filename : fullpath); + } + + if (file->fd == -1 || !isZzzFile) + { + if (trace_all || trace_files) ffnx_info("Fallback to Steam path mode %s\n", is_redirected ? _filename : fullpath); + + file->fd = ff8_externals._sopen(is_redirected ? _filename : fullpath, oflag, _SH_DENYNO, pmode); + + if (file->fd == -1) + { + if (trace_all || trace_files) ffnx_info("Fallback to original path mode %s\n", fullpath); + } + } + } + + if (file->fd == -1) + { + bool is_redirected = ff8_attempt_redirection(fullpath, _filename, sizeof(_filename)); + + last_fopen_is_redirected = is_redirected; + + // We need to use the external _open, and not the official one + file->fd = ff8_externals._sopen(is_redirected ? _filename : fullpath, oflag, shflag, pmode); - last_fopen_is_redirected = false; + last_fopen_is_redirected = false; + } } file->is_open = 1; @@ -433,3 +701,122 @@ bool ff8_fs_last_fopen_is_redirected() { return last_fopen_is_redirected; } + +bool ff8_steam_redirection(const char *lpFileName, char *newPath, bool *isZzzFile) +{ + bool redirected = false; + + if (isZzzFile != nullptr) + { + *isZzzFile = false; + } + + if (strstr(lpFileName, "CD:") != NULL) + { + uint8_t requiredDisk = (*ff8_externals.savemap_field)->curr_disk; + CHAR diskAsChar[2]; + + itoa(requiredDisk, diskAsChar, 10); + + // Search for the last '\' character and get a pointer to the next char + const char* pos = strrchr(lpFileName, 92) + 1; + + if (strstr(lpFileName, "DISK1") != NULL || strstr(lpFileName, "DISK2") != NULL || strstr(lpFileName, "DISK3") != NULL || strstr(lpFileName, "DISK4") != NULL) + { + if (isZzzFile == nullptr) + { + strcpy(newPath, ff8_externals.app_path); + } + PathAppendA(newPath, R"(data\disk)"); + PathAppendA(newPath, pos); + + if (strstr(lpFileName, diskAsChar) != NULL) + { + redirected = true; + } + } + + if (isZzzFile != nullptr) + { + *isZzzFile = true; + } + } + else if (strstr(lpFileName, "app.log") || strstr(lpFileName, "ff8input.cfg")) + { + // Search for the last '\' character and get a pointer to the next char + const char* pos = strrchr(lpFileName, 92) + 1; + + get_userdata_path(newPath, MAX_PATH, false); + PathAppendA(newPath, JP_VERSION ? "ff8input_jp.cfg" : pos); + + redirected = true; + } + else if (strstr(lpFileName, "temp.fi") || strstr(lpFileName, "temp.fl") || strstr(lpFileName, "temp.fs") || strstr(lpFileName, "temp_evn.") || strstr(lpFileName, "temp_odd.")) + { + // Search for the last '\' character and get a pointer to the next char + const char* pos = strrchr(lpFileName, 92) + 1; + + get_userdata_path(newPath, MAX_PATH, false); + PathAppendA(newPath, pos); + + redirected = true; + } + else if (strstr(lpFileName, ".fi") != NULL || strstr(lpFileName, ".fl") != NULL || strstr(lpFileName, ".fs") != NULL) + { + // Search for the last '\' character and get a pointer to the next char + const char* pos = strrchr(lpFileName, 92) + 1; + + if (remastered_edition && (strstr(lpFileName, "field") != NULL || strstr(lpFileName, "magic") != NULL || strstr(lpFileName, "world") != NULL)) + { + PathAppendA(newPath, R"(data)"); + } + else + { + get_data_lang_path(newPath, isZzzFile == nullptr); + } + + PathAppendA(newPath, pos); + + if (isZzzFile != nullptr) + { + *isZzzFile = true; + } + + redirected = true; + } + else if (StrStrIA(lpFileName, R"(SAVE\)") != NULL) // SAVE\SLOTX\saveN or save\chocorpg + { + CHAR saveFileName[50]{ 0 }; + + // Search for the next character pointer after "SAVE\" + const char* pos = StrStrIA(lpFileName, R"(SAVE\)") + 5; + strcpy(saveFileName, pos); + _strlwr(saveFileName); + char* posSeparator = strstr(saveFileName, R"(\)"); + if (posSeparator != NULL) + { + *posSeparator = '_'; + } + strcat(saveFileName, R"(.ff8)"); + + get_userdata_path(newPath, MAX_PATH, true); + PathAppendA(newPath, saveFileName); + + redirected = true; + } + else if (isZzzFile != nullptr) + { + if (strncmp(lpFileName, ff8_externals.app_path, strlen(ff8_externals.app_path)) == 0) { + // Remove app_path + strcpy(newPath, lpFileName + strlen(ff8_externals.app_path) + 1); + + redirected = true; + } + + *isZzzFile = true; + } + + if (redirected && (trace_all || trace_files)) ffnx_info("Redirected: %s -> %s (is in ZZZ archive: %s)\n", lpFileName, newPath, isZzzFile == nullptr ? "not asked" : (*isZzzFile ? "yes" : "no")); + + return redirected; +} diff --git a/src/ff8/file.h b/src/ff8/file.h index e91f8d49..7c097f65 100644 --- a/src/ff8/file.h +++ b/src/ff8/file.h @@ -35,9 +35,19 @@ void ff8_fs_archive_patch_compression(uint32_t compression_type); uint8_t *ff8_fs_archive_malloc_source_data(size_t size, char *source_code_path, int line); uint8_t *ff8_fs_archive_malloc_target_data(size_t size, char *source_code_path, int line); void ff8_fs_archive_uncompress_data(const uint8_t *source_data, uint8_t *target_data); +void ff8_fs_archive_field_concat_extension(char *fileName, char *extension); +// io (low level) int ff8_open(const char *fileName, int oflag, ...); +int ff8_read(int fd, void *buffer, unsigned int bufferSize); +int ff8_write(int fd, void *buffer, unsigned int bufferSize); +__int32 ff8_lseek(int fd, __int32 offset, int whence); +__int32 ff8_filelength(int fd); +int ff8_close(int fd); +// stdio (low level) FILE *ff8_fopen(const char *fileName, const char *mode); + +// FF8 (opens direct file or FS archive) ff8_file *ff8_open_file(ff8_file_context *infos, const char *fs_path); uint32_t(*ff8_read_file)(uint32_t count, void* buffer, struct ff8_file* file); void (*ff8_close_file)(struct ff8_file* file); @@ -45,3 +55,4 @@ void (*ff8_close_file)(struct ff8_file* file); void ff8_fs_lang_string(char *data); bool ff8_fs_last_fopen_is_redirected(); +bool ff8_steam_redirection(const char *path, char *out, bool *is_zzz_file = nullptr); diff --git a/src/ff8/mod.cpp b/src/ff8/mod.cpp index 064e354c..ec290a9d 100644 --- a/src/ff8/mod.cpp +++ b/src/ff8/mod.cpp @@ -27,6 +27,7 @@ #include "mod.h" #include "file.h" +#include "remaster.h" bx::DefaultAllocator TextureImage::defaultAllocator; @@ -49,6 +50,23 @@ bool TextureImage::createImage(const char *filename, int originalTexturePixelWid if (extension != nullptr && stricmp(extension + 1, "png") == 0) { // Load PNG using libPNG _image = loadPng(&defaultAllocator, filename, targetFormat); + + // Remastered fix (Convert 768x288 to 768x384) + if (remastered_edition && _image != nullptr && _image->m_width == 768 && _image->m_height == 288 && _image->m_width == originalTexturePixelWidth * 3 * 2 && _image->m_height < originalTextureHeight * 3) { + bimg::ImageContainer *resizedImage = bimg::imageAlloc(&defaultAllocator, targetFormat, _image->m_width, 384, _image->m_depth, _image->m_numLayers, false, false); + + if (resizedImage == nullptr) { + ffnx_error("%s: cannot resize PNG file %s\n", __func__, filename); + return false; + } + + memcpy(resizedImage->m_data, _image->m_data, _image->m_size); + memset((uint8_t *)resizedImage->m_data + _image->m_size, 0, (resizedImage->m_height - _image->m_height) * resizedImage->m_width * 4); + + destroyImage(); + _image = resizedImage; + } + setLod(0); } else if (extension != nullptr && stricmp(extension + 1, "dds") == 0) { // Load DDS using DirectXTex @@ -166,6 +184,12 @@ uint8_t TextureImage::computeScale(int sourcePixelW, int sourceH, const char *fi int scaleW = targetPixelW / sourcePixelW, scaleH = targetH / sourceH; + // Remastered textures + if (scaleW == scaleH * 2) + { + scaleW = scaleH; + } + if (scaleW != scaleH) { ffnx_warning("External texture size must have the same ratio as the original texture: (%d / %d) filename=%s\n", sourcePixelW, sourceH, filename); @@ -188,6 +212,60 @@ ModdedTexture::ModdedTexture(const TexturePacker::IdentifiedTexture &originalTex { } +bool ModdedTexture::findExternalTexture(char *outFilename, uint8_t palette_index, bool hasPal, const char *extension, char *foundExtension) const +{ + if (findExternalTexture(originalTexture().name().c_str(), outFilename, palette_index, hasPal, extension, foundExtension)) + { + return true; + } + + std::string remasterName = originalTexture().remasteredName(); + if (remasterName.empty()) + { + return false; + } + + if (findExternalTextureRemastered(remasterName.c_str(), outFilename, palette_index, hasPal, extension, foundExtension)) + { + return true; + } + + size_t pos = remasterName.find("field_hd_new"); + if (pos != std::string::npos && findExternalTextureRemastered(remasterName.replace(pos, sizeof("field_hd_new"), "field_hd").c_str(), outFilename, palette_index, hasPal, extension, foundExtension)) + { + return true; + } + + if (trace_all || trace_loaders) ffnx_warning("Texture does not exist, skipping: %s\n", outFilename); + + return false; +} + +bool ModdedTexture::findExternalTextureRemastered(const char *name, char *filename, uint8_t palette_index, bool hasPal, const char *extension, char *found_extension) +{ + // Retry with remaster name + if (findExternalTexture(name, filename, palette_index, hasPal, extension, found_extension)) + { + return true; + } + + // Retry inside ZZZ archive + _snprintf(filename, MAX_PATH, "textures\\%s.png", name); + + if (remastered_edition && g_FF8ZzzArchiveMain.fileExists(filename)) + { + _snprintf(filename, MAX_PATH, "zzz://textures\\%s.png", name); + + if (trace_all || trace_loaders) ffnx_trace("Using texture: %s\n", filename); + + if (found_extension != nullptr) { + strncpy(found_extension, "png", 3); + } + + return true; + } +} + bool ModdedTexture::findExternalTexture(const char *name, char *filename, uint8_t palette_index, bool hasPal, const char *extension, char *found_extension) { char langPath[16] = "/"; @@ -316,7 +394,7 @@ bool TextureModStandard::createImages(int paletteCount, int internalLodScale) char filename[MAX_PATH] = {}, *extension = nullptr, found_extension[16] = {}; for (int paletteId = 0; paletteId < modCount; ++paletteId) { - if (!findExternalTexture(originalTexture().name().c_str(), filename, paletteId, true, extension, found_extension)) + if (!findExternalTexture(filename, paletteId, true, extension, found_extension)) { continue; } @@ -490,7 +568,7 @@ bool TextureBackground::createImages(const char *extension, char *foundExtension char filename[MAX_PATH] = {}; - if (!findExternalTexture(originalTexture().name().c_str(), filename, 0, false, extension, foundExtension)) + if (!findExternalTexture(filename, 0, false, extension, foundExtension)) { return false; } diff --git a/src/ff8/mod.h b/src/ff8/mod.h index 4673f65f..78f66c06 100644 --- a/src/ff8/mod.h +++ b/src/ff8/mod.h @@ -80,6 +80,8 @@ class ModdedTexture { uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, int16_t paletteVramX, int16_t paletteVramY ) const=0; + bool findExternalTexture(char *outFilename, uint8_t palette_index, bool hasPal, const char *extension = nullptr, char *foundExtension = nullptr) const; + static bool findExternalTextureRemastered(const char *name, char *filename, uint8_t palette_index, bool hasPal, const char *extension = nullptr, char *found_extension = nullptr); static bool findExternalTexture(const char *name, char *outFilename, uint8_t palette_index, bool hasPal, const char *extension = nullptr, char *foundExtension = nullptr); protected: static void drawImage( diff --git a/src/ff8/movies.cpp b/src/ff8/movies.cpp index 4b223f0b..78d1da37 100644 --- a/src/ff8/movies.cpp +++ b/src/ff8/movies.cpp @@ -26,6 +26,7 @@ #include "../globals.h" #include "../log.h" +#include "remaster.h" struct FF8Bink { pak_pointers_entry entry; @@ -149,3 +150,85 @@ void ff8_bink_close(void *opaque) delete f; } + +void *ff8_zzz_open(const char *fmv_name) +{ + return g_FF8ZzzArchiveOther.openFile(fmv_name); +} + +int ff8_zzz_read(void *opaque, uint8_t *buf, int buf_size) +{ + if (trace_all) ffnx_trace("%s: buf_size=%d\n", __func__, buf_size); + + if (opaque == nullptr) { + return AVERROR_EXIT; + } + + if (buf_size == 0) { + return 0; + } + + Zzz::File *f = (Zzz::File *)opaque; + + int r = f->read(buf, buf_size); + + if (r == 0) { + return AVERROR_EOF; + } + + if (r < 0) { + return AVERROR_EXIT; + } + + return r; +} + +int64_t ff8_zzz_seek(void *opaque, int64_t offset, int whence) +{ + if (trace_all) ffnx_trace("%s: offset=%d, whence=%d\n", __func__, offset, whence); + + if (opaque == nullptr) { + return AVERROR_EXIT; + } + + Zzz::File *f = (Zzz::File *)opaque; + + if (whence == AVSEEK_SIZE) { + return int64_t(f->size()); + } + + whence &= 0xFFFF; + + bx::Whence::Enum zzzWhence = bx::Whence::Begin; + + if (whence == SEEK_END) { + zzzWhence = bx::Whence::End; + } else if (whence == SEEK_CUR) { + zzzWhence = bx::Whence::Current; + } else if (whence != SEEK_SET) { + ffnx_error("%s: seek type not supported: %d\n", __func__, whence); + + return AVERROR_EXIT; + } + + int64_t pos = f->seek(offset, zzzWhence); + + if (pos < 0) { + return AVERROR_EXIT; + } + + return pos; +} + +void ff8_zzz_close(void *opaque) +{ + if (trace_all || trace_files) ffnx_trace("%s\n", __func__); + + if (opaque == nullptr) { + return; + } + + Zzz::File *f = (Zzz::File *)opaque; + + Zzz::closeFile(f); +} diff --git a/src/ff8/movies.h b/src/ff8/movies.h index 86c4ad06..ba53e1aa 100644 --- a/src/ff8/movies.h +++ b/src/ff8/movies.h @@ -28,3 +28,9 @@ void *ff8_bink_open(uint8_t disc, uint32_t movie); int ff8_bink_read(void *opaque, uint8_t *buf, int buf_size); int64_t ff8_bink_seek(void *opaque, int64_t offset, int whence); void ff8_bink_close(void *opaque); + +void *ff8_zzz_open(const char *fmv_name); +int ff8_zzz_read(void *opaque, uint8_t *buf, int buf_size); +int64_t ff8_zzz_seek(void *opaque, int64_t offset, int whence); +void ff8_zzz_close(void *opaque); + diff --git a/src/ff8/remaster.cpp b/src/ff8/remaster.cpp new file mode 100644 index 00000000..7685cf1e --- /dev/null +++ b/src/ff8/remaster.cpp @@ -0,0 +1,387 @@ +/****************************************************************************/ +// 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) 2022 myst6re // +// Copyright (C) 2022 Julian Xhokaxhiu // +// Copyright (C) 2022 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 "../globals.h" +#include "../log.h" +#include "../patch.h" +#include "remaster.h" +#include "vram.h" + +Zzz g_FF8ZzzArchiveMain; +Zzz g_FF8ZzzArchiveOther; +uint8_t *extended_memory = nullptr; + +struct ff8_remastered_model_divisor { + char modelId[5]; + uint16_t divisor; +}; + +std::unordered_map field_model_map; +double next_field_model_divisor = 0.0; +constexpr int model_divisors_size = 142; +ff8_remastered_model_divisor model_divisors[model_divisors_size] = { + {"d000", 104}, + {"d001", 75}, + {"d002", 75}, + {"d003", 75}, + {"d005", 75}, + {"d006", 75}, + {"d007", 75}, + {"d009", 75}, + {"d010", 75}, + {"d011", 75}, + {"d012", 75}, + {"d014", 75}, + {"d015", 74}, + {"d016", 74}, + {"d017", 74}, + {"d018", 100}, + {"d019", 100}, + {"d020", 100}, + {"d021", 100}, + {"d022", 104}, + {"d023", 104}, + {"d024", 104}, + {"d025", 104}, + {"d026", 104}, + {"d027", 104}, + {"d028", 104}, + {"d029", 104}, + {"d030", 104}, + {"d032", 70}, + {"d033", 70}, + {"d034", 70}, + {"d037", 70}, + {"d040", 74}, + {"d041", 74}, + {"d043", 75}, + {"d044", 75}, + {"d045", 80}, + {"d046", 80}, + {"d047", 47}, + {"d048", 47}, + {"d049", 75}, + {"d050", 100}, + {"d051", 104}, + {"d052", 104}, + {"d054", 500}, + {"d055", 500}, + {"d056", 500}, + {"d057", 500}, + {"d058", 500}, + {"d059", 840}, + {"d060", 150}, + {"d061", 150}, + {"d062", 150}, + {"d065", 70}, + {"d066", 150}, + {"d067", 150}, + {"d068", 150}, + {"d069", 150}, + {"d070", 150}, + {"d071", 75}, + {"d072", 80}, + {"d073", 47}, + {"d074", 74}, + {"d075", 104}, + {"n002", 19}, + {"n010", 55}, + {"n029", 7}, + {"o028", 300}, + {"o029", 300}, + {"p001", 150}, + {"p002", 150}, + {"p004", 150}, + {"p005", 150}, + {"p006", 150}, + {"p007", 150}, + {"p008", 150}, + {"p010", 150}, + {"p011", 150}, + {"p012", 6}, + {"p013", 150}, + {"p015", 150}, + {"p017", 150}, + {"p018", 62}, + {"p021", 150}, + {"p022", 150}, + {"p024", 150}, + {"p025", 150}, + {"p028", 150}, + {"p030", 150}, + {"p031", 150}, + {"p034", 150}, + {"p037", 150}, + {"p042", 150}, + {"p043", 150}, + {"p044", 150}, + {"p045", 150}, + {"p047", 150}, + {"p048", 300}, + {"p049", 150}, + {"p056", 150}, + {"p058", 300}, + {"p061", 150}, + {"p063", 150}, + {"p064", 150}, + {"p070", 150}, + {"p074", 150}, + {"p078", 150}, + {"p086", 150}, + {"p087", 150}, + {"p090", 150}, + {"p094", 150}, + {"p101", 150}, + {"p105", 150}, + {"p106", 150}, + {"p107", 150}, + {"p119", 150}, + {"p120", 150}, + {"p121", 150}, + {"p122", 500}, + {"p123", 32}, + {"p124", 150}, + {"p125", 75}, + {"p133", 150}, + {"p137", 67}, + {"p138", 150}, + {"p142", 45}, + {"p143", 150}, + {"p144", 150}, + {"p147", 150}, + {"p161", 150}, + {"p162", 150}, + {"p163", 150}, + {"p164", 150}, + {"p168", 150}, + {"p169", 150}, + {"p170", 150}, + {"p171", 150}, + {"p172", 150}, + {"p173", 45}, + {"p180", 150}, + {"p183", 150} +}; + +void field_model_vertices_scale() +{ + double unzoom_model = next_field_model_divisor; + + if (unzoom_model == 1.0) { + return ((void(*)())ff8_externals.model_vertices_scale_sub_45FE10)(); + } + + int flags = 0; + int v1 = ff8_externals.camera_zoom_dword_1CA92E4[0] >> 1; + ff8_externals.dword_1CA8A50[0] = ff8_externals.dword_1CA8A50[3]; + double v30 = (double)ff8_externals.camera_zoom_dword_1CA92E4[0]; // Float in the original implementation + int *v2 = ff8_externals.dword_1CA8A50 + 1; + uint32_t *v22 = (uint32_t *)ff8_externals.dword_1CA8A30 + 4; + uint32_t v31 = 8 * ff8_externals.word_1CA92DE[0]; + uint32_t v32 = 8 * ff8_externals.word_1CA92DE[2]; + int32_t x_related; + int32_t y_related; + uint32_t z_related; + double v11; + int16_t *v3 = ff8_externals.dword_1CA8A10; + + for (int i = 0; i < 3; ++i) { + double v4 = double(v3[0]), v6 = double(v3[1]), v7 = double(v3[2]); + v3 += 4; + + x_related = ff8_externals.dword_1CA9290[0] + round(double(ff8_externals.flt_1CA9234[0] * v7 + ff8_externals.flt_1CA9234[1] * v6 + ff8_externals.flt_1CA9234[2] * v4) / unzoom_model); + y_related = ff8_externals.dword_1CA9290[1] + round(double(ff8_externals.flt_1CA9234[3] * v7 + ff8_externals.flt_1CA9234[4] * v6 + ff8_externals.flt_1CA9234[5] * v4) / unzoom_model); + z_related = ff8_externals.dword_1CA9290[2] + round(double(ff8_externals.flt_1CA9234[6] * v7 + ff8_externals.flt_1CA9234[7] * v6 + ff8_externals.flt_1CA9234[8] * v4) / unzoom_model); + + uint32_t z_related2 = z_related; + if (z_related2 > 0xFFFF) { + z_related2 = 0xFFFF; + flags |= 0x80040000; + } + *v2 = uint16_t(z_related2); + if (*v2 <= v1) { + if (!*v2) { + flags |= 0x80040000; + } + z_related2 = v1; + flags |= 0x80020000; + } + v11 = v30 / z_related2; + double v20 = round(8.0 * x_related * v11); + int v12 = v31 + int(v20); + if (v12 < -8192) { + v12 = -8192; + flags |= 0x80004000; + } else if (v12 > 8184) { + v12 = 8184; + flags |= 0x80004000; + } + + double v21 = round(8.0 * y_related * v11); + int v14 = v32 + int(v21); + if (v14 < -8192) { + v14 = -8192; + flags |= 0x80002000; + } else if (v14 > 8184) { + v14 = 8184; + flags |= 0x80002000; + } + + *v22 = (uint32_t(uint16_t(v14)) << 16) | uint16_t(v12); + ++v22; + ++v2; + } + + double v16 = v11 * (double)*(int16_t *)(ff8_externals.camera_zoom_dword_1CA92E4 + 2) * 65536.0 + (double)*(int32_t *)(ff8_externals.camera_zoom_dword_1CA92E4 + 8); + double v33 = v16 * 0.00024414062; + int v17 = int(round(v33)); + if (v17 > 0xFFF) { + v17 = 0xFFF; + flags |= 0x1000u; + } + ff8_externals.calc_model_poly_condition_result_dword_1CA8A70[0] = int(round(v16)); + ff8_externals.dword_1CA8A30[0] = v17; + ff8_externals.calc_model_poly_condition_result_dword_1CA8A70[1] = x_related; + if (x_related >= -32768) { + if (x_related <= 0x7FFF) { + ff8_externals.dword_1CA8A30[1] = x_related; + } else { + flags |= 0x81000000; + ff8_externals.dword_1CA8A30[1] = 0x7FFF; + } + } else { + flags |= 0x81000000; + ff8_externals.dword_1CA8A30[1] = -32768; + } + ff8_externals.calc_model_poly_condition_result_dword_1CA8A70[2] = y_related; + if (y_related >= -32768) { + if (y_related <= 0x7FFF) { + ff8_externals.dword_1CA8A30[2] = y_related; + } else { + flags |= 0x80800000; + ff8_externals.dword_1CA8A30[2] = 0x7FFF; + } + } else { + flags |= 0x80800000; + ff8_externals.dword_1CA8A30[2] = -32768; + } + ff8_externals.calc_model_poly_condition_result_dword_1CA8A70[3] = z_related; + if (z_related >= -32768) { + if (z_related <= 0x7FFF) { + ff8_externals.dword_1CA8A30[3] = z_related; + } else { + ff8_externals.dword_1CA8A30[3] = 0x7FFF; + flags |= 0x400000; + } + } else { + ff8_externals.dword_1CA8A30[3] = -32768; + flags |= 0x400000; + } + + *ff8_externals.dword_1CA92F8 = flags; +} + +double get_model_divisor(uint32_t addr) +{ + auto it = field_model_map.find(addr); + if (it == field_model_map.end()) { + return 1.0; + } + + for (int i = 0; i < model_divisors_size; ++i) { + char *modelId = model_divisors[i].modelId; + + if (*(uint32_t *)modelId == *(uint32_t *)it->second.name) { + return double(model_divisors[i].divisor); + } + } + + return 1.0; +} + +void field_sub_530C30(int a1, int a2, int **a3) +{ + next_field_model_divisor = get_model_divisor(uint32_t(*a3)); + + ((void(*)(int,int,int**))ff8_externals.sub_530C30)(a1, a2, a3); + + next_field_model_divisor = 1.0; +} + +int field_open_chara_one( + int current_data_pointer, + void *models_infos, + void *palette_vram_positions, + void *texture_vram_positions, + const char *dir_name, + int field_data_pointer, + int allocated_size, + int pcb_data_size +) { + int ret = ((int(*)(int,void*,void*,void*,const char*,int,int,int))ff8_externals.load_field_models)(current_data_pointer, models_infos, palette_vram_positions, texture_vram_positions, dir_name, field_data_pointer, allocated_size, pcb_data_size); + + std::map models_ordered; + + for (auto model: chara_one_models) { + models_ordered.insert(std::pair(model.second.modelId, model.second)); + } + + field_model_map.clear(); + + for (int i = 0; i < *ff8_externals.field_state_other_count; ++i) { + int16_t model_id = (*ff8_externals.field_state_others)[i].model_id; + + if (model_id >= 0) { + field_model_map.insert(std::pair(((uint32_t *)ff8_externals.dword_1DCB340)[i], models_ordered.at(model_id))); + } + } + + return ret; +} + +void ff8_remaster_init() +{ + replace_call(ff8_externals.sub_533C30 + 0x44, field_model_vertices_scale); + replace_call(ff8_externals.sub_533CD0 + 0x28E, field_sub_530C30); + replace_call(ff8_externals.read_field_data + (JP_VERSION ? 0xFA2 : 0xF0F), field_open_chara_one); + + char fullpath[MAX_PATH]; + + snprintf(fullpath, sizeof(fullpath), "%s/main.zzz", ff8_externals.app_path); + errno_t err = g_FF8ZzzArchiveMain.open(fullpath); + + if (err != 0) { + ffnx_error("%s: cannot open %s (error code %d)\n", __func__, fullpath, err); + exit(1); + } + + snprintf(fullpath, sizeof(fullpath), "%s/other.zzz", ff8_externals.app_path); + + err = g_FF8ZzzArchiveOther.open(fullpath); + + if (err != 0) { + ffnx_error("%s: cannot open %s (error code %d)\n", __func__, fullpath, err); + exit(1); + } +} diff --git a/src/ff8/remaster.h b/src/ff8/remaster.h new file mode 100644 index 00000000..28782d1b --- /dev/null +++ b/src/ff8/remaster.h @@ -0,0 +1,32 @@ +/****************************************************************************/ +// 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) 2022 myst6re // +// Copyright (C) 2022 Julian Xhokaxhiu // +// Copyright (C) 2022 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 + +#include "zzz_archive.h" + +void ff8_remaster_init(); + +extern Zzz g_FF8ZzzArchiveMain; +extern Zzz g_FF8ZzzArchiveOther; diff --git a/src/ff8/texture_packer.cpp b/src/ff8/texture_packer.cpp index 16ae70b6..461b2797 100644 --- a/src/ff8/texture_packer.cpp +++ b/src/ff8/texture_packer.cpp @@ -147,14 +147,16 @@ void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y } } -bool TexturePacker::setTexture(const char *name, const TextureInfos &texture, const TextureInfos &palette, int textureCount, bool clearOldTexture) +bool TexturePacker::setTexture(const char *name, const char *remasteredName, const TextureInfos &texture, const TextureInfos &palette, int textureCount, bool clearOldTexture) { - bool hasNamedTexture = name != nullptr && *name != '\0'; + bool hasNamedTexture = name != nullptr && *name != '\0', + hasRemasteredNamedTexture = remasteredName != nullptr && *remasteredName != '\0'; - if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s xBpp2=%d y=%d wBpp2=%d h=%d bpp=%d xPal=%d yPal=%d wPal=%d hPal=%d textureCount=%d clearOldTexture=%d\n", __func__, + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s xBpp2=%d y=%d wBpp2=%d h=%d bpp=%d xPal=%d yPal=%d wPal=%d hPal=%d textureCount=%d clearOldTexture=%d remastered=%s\n", __func__, hasNamedTexture ? name : "N/A", texture.x(), texture.y(), texture.w(), texture.h(), texture.bpp(), - palette.x(), palette.y(), palette.w(), palette.h(), textureCount, clearOldTexture); + palette.x(), palette.y(), palette.w(), palette.h(), textureCount, clearOldTexture, + hasRemasteredNamedTexture ? remasteredName : "N/A"); ModdedTextureId textureId = makeTextureId(texture.x(), texture.y()); setVramTextureId(textureId, texture.x(), texture.y(), texture.w(), texture.h(), clearOldTexture); @@ -164,7 +166,7 @@ bool TexturePacker::setTexture(const char *name, const TextureInfos &texture, co setVramTextureId(makeTextureId(texture.x(), texture.y(), true), palette.x(), palette.y(), palette.w(), palette.h(), clearOldTexture); } - IdentifiedTexture tex(name, texture, palette); + IdentifiedTexture tex(name, texture, palette, remasteredName); if (hasNamedTexture && textureCount != 0) { @@ -348,7 +350,7 @@ void TexturePacker::animateTextureByCopy(int sourceXBpp2, int sourceY, int sourc { dynamic_cast(itTarget->second.mod())->forceCurrentPalette(sourceY - it->second.palette().y()); } - else if (it->second.mod() != nullptr && it->second.mod()->canCopyRect()) + else if (it->second.mod() != nullptr && it->second.mod()->canCopyRect() && it->second.remasteredName().empty()) { dynamic_cast(it->second.mod())->copyRect( sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY, @@ -363,7 +365,7 @@ void TexturePacker::animateTextureByCopy(int sourceXBpp2, int sourceY, int sourc { dynamic_cast(it->second.mod())->forceCurrentPalette(sourceY - it->second.palette().y()); } - else + else if (it->second.remasteredName().empty()) { dynamic_cast(it->second.mod())->copyRect( sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY @@ -785,7 +787,7 @@ TexturePacker::TiledTex::TiledTex( } TexturePacker::IdentifiedTexture::IdentifiedTexture() : - _texture(TextureInfos()), _palette(TextureInfos()), _name(""), _mod(nullptr), + _texture(TextureInfos()), _palette(TextureInfos()), _name(""), _remasteredName(""), _mod(nullptr), _frameId(-1), _isAnimated(false) { } @@ -793,8 +795,10 @@ TexturePacker::IdentifiedTexture::IdentifiedTexture() : TexturePacker::IdentifiedTexture::IdentifiedTexture( const char *name, const TextureInfos &texture, - const TextureInfos &palette -) : _texture(texture), _palette(palette), _name(name == nullptr ? "" : name), _mod(nullptr), + const TextureInfos &palette, + const char *remasteredName +) : _texture(texture), _palette(palette), _name(name == nullptr ? "" : name), + _remasteredName(remasteredName == nullptr ? "" : remasteredName), _mod(nullptr), _frameId(-1), _isAnimated(false) { } diff --git a/src/ff8/texture_packer.h b/src/ff8/texture_packer.h index 4a58320e..7d09c53a 100644 --- a/src/ff8/texture_packer.h +++ b/src/ff8/texture_packer.h @@ -105,7 +105,8 @@ class TexturePacker { IdentifiedTexture( const char *name, const TextureInfos &texture, - const TextureInfos &palette = TextureInfos() + const TextureInfos &palette = TextureInfos(), + const char *remasteredName = nullptr ); void setMod(ModdedTexture *mod); inline ModdedTexture *mod() const { @@ -124,6 +125,9 @@ class TexturePacker { inline const std::string &name() const { return _name; } + inline const std::string &remasteredName() const { + return _remasteredName; + } inline const char *printableName() const { return _name.empty() ? "N/A" : _name.c_str(); } @@ -140,7 +144,7 @@ class TexturePacker { } private: TextureInfos _texture, _palette; - std::string _name; + std::string _name, _remasteredName; ModdedTexture *_mod; std::unordered_map _redirections; int _frameId; @@ -155,7 +159,7 @@ class TexturePacker { }; explicit TexturePacker(); - bool setTexture(const char *name, const TextureInfos &texture, const TextureInfos &palette = TextureInfos(), int textureCount = -1, bool clearOldTexture = true); + bool setTexture(const char *name, const char *remasteredName, const TextureInfos &texture, const TextureInfos &palette = TextureInfos(), int textureCount = -1, bool clearOldTexture = true); bool setTextureBackground(const char *name, int x, int y, int w, int h, int maxW, const std::vector &mapTiles, const char *extension = nullptr, char *found_extension = nullptr); // Override a part of the VRAM from another part of the VRAM, typically with biggest textures (Worldmap) bool setTextureRedirection(const char *name, const TextureInfos &oldTexture, const TextureInfos &newTexture, const Tim &tim); diff --git a/src/ff8/vram.cpp b/src/ff8/vram.cpp index 2436412c..90307008 100644 --- a/src/ff8/vram.cpp +++ b/src/ff8/vram.cpp @@ -42,6 +42,7 @@ TexturePacker texturePacker; char next_texture_name[MAX_PATH] = ""; +char next_remastered_texture_name[MAX_PATH] = ""; TexturePacker::TextureInfos texture_infos = TexturePacker::TextureInfos(); TexturePacker::TextureInfos palette_infos = TexturePacker::TextureInfos(); uint16_t *next_pal_data = nullptr; @@ -77,6 +78,7 @@ int wm_selphie_old_second_texture_pos = 0; int wm_selphie_new_second_texture_pos = 0; // Battle char battle_texture_name[MAX_PATH] = ""; +int battle_file_id = 0; int battle_texture_id = 0; Stage stage; struct BattleTextureHeader { @@ -230,17 +232,18 @@ void ff8_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) } if (texture_infos.isValid() && palette_infos.isValid()) { - texturePacker.setTexture(next_texture_name, texture_infos, palette_infos, next_texture_count, !next_do_not_clear_old_texture); + texturePacker.setTexture(next_texture_name, next_remastered_texture_name, texture_infos, palette_infos, next_texture_count, !next_do_not_clear_old_texture); texture_infos = TexturePacker::TextureInfos(); palette_infos = TexturePacker::TextureInfos(); } else if (!texture_infos.isValid() && !palette_infos.isValid() && !isPal) { - texturePacker.setTexture(next_texture_name, TexturePacker::TextureInfos(x, y, w, h, next_bpp), TexturePacker::TextureInfos(), next_texture_count, !next_do_not_clear_old_texture); + texturePacker.setTexture(next_texture_name, next_remastered_texture_name, TexturePacker::TextureInfos(x, y, w, h, next_bpp), TexturePacker::TextureInfos(), next_texture_count, !next_do_not_clear_old_texture); } ff8_externals.sub_464850(x, y, x + w - 1, h + y - 1); *next_texture_name = '\0'; + *next_remastered_texture_name = '\0'; next_do_not_clear_old_texture = false; next_texture_count = -1; } @@ -410,6 +413,37 @@ const std::string &tex_name_special_cases(std::string &filename, const std::stri return filename; } +const std::string &tex_name_special_cases_remastered(std::string &filename, const std::string &full_filename, int vram_id) +{ + if (full_filename.starts_with("cards\\back") || full_filename.starts_with("cards\\board")) { + filename = full_filename; + filename.append("_"); + filename.append(std::to_string(vram_id / 4)); // Adjusting number + } else if (full_filename.starts_with("cards\\text")) { + // Add lang + char langPath[16] = {}; + concat_lang_str(langPath); + if (strncmp(langPath, "en", sizeof("en")) != 0) { + filename.append("_"); + filename.append(langPath); + } + } else if (vram_id >= 20 && full_filename.starts_with("ff8logo\\")) { + // Add lang + char langPath[16] = {}; + concat_lang_str(langPath); + if (JP_VERSION) { + filename.append("_jp"); + } else { + filename.append("_efigs"); + } + } else if (vram_id == 15 && full_filename.starts_with("field.fs\\field_hd_new\\wmset_014")) { + filename = full_filename; + filename.append("_0"); // Adjusting number + } + + return filename; +} + void set_tex_name(const TexturePacker::TiledTex &tiledTex, ff8_tex_header *tex_header) { if (tex_header == nullptr) @@ -424,7 +458,7 @@ void set_tex_name(const TexturePacker::TiledTex &tiledTex, ff8_tex_header *tex_h return; } - std::string filename; + std::string filename, remasteredFilename; int file_count = 0; int start_vram_id = 0; @@ -432,6 +466,9 @@ void set_tex_name(const TexturePacker::TiledTex &tiledTex, ff8_tex_header *tex_h { if (tex.isValid()) { + if (remasteredFilename.empty() && !tex.remasteredName().empty()) { + remasteredFilename.append(tex.remasteredName()); + } if (file_count == 0) { filename.append(tex.name()); @@ -492,11 +529,23 @@ void set_tex_name(const TexturePacker::TiledTex &tiledTex, ff8_tex_header *tex_h if (trace_all || trace_vram) ffnx_trace("%s: %s\n", __func__, filename.c_str()); - tex_header->file.pc_name = (char*)external_malloc(1024); + tex_header->file.pc_name = (char*)external_calloc(1024, sizeof(char)); if (tex_header->file.pc_name != nullptr) { - strncpy(tex_header->file.pc_name, filename.c_str(), 1024); + strncpy(tex_header->file.pc_name, filename.c_str(), 511); + + if (!remasteredFilename.empty()) { + full_filename = remasteredFilename; + remasteredFilename.append("_"); + remasteredFilename.append(std::to_string(vramId)); + + remasteredFilename = tex_name_special_cases_remastered(remasteredFilename, full_filename, vramId); + + if (trace_all || trace_vram) ffnx_trace("%s: %s (alternative)\n", __func__, remasteredFilename.c_str()); + + strncpy(tex_header->file.pc_name + 512, remasteredFilename.c_str(), 511); + } } } } @@ -619,6 +668,15 @@ uint32_t ff8_credits_open_texture(char *fileName, char *buffer) // {name}.lzs strncpy(next_texture_name, strrchr(fileName, '\\') + 1, sizeof(next_texture_name)); + if (!is_remastered_hd_textures_disabled("intro")) { + if (strstr(fileName, "ff8.lzs")) { + snprintf(next_remastered_texture_name, sizeof(next_remastered_texture_name), "ff8logo\\%s", strrchr(fileName, '\\') + 1); + } else if (strstr(fileName, "square.lzs")) { + snprintf(next_remastered_texture_name, sizeof(next_remastered_texture_name), "squarelogo\\%s", strrchr(fileName, '\\') + 1); + } else { + snprintf(next_remastered_texture_name, sizeof(next_remastered_texture_name), "opening\\%s", strrchr(fileName, '\\') + 1); + } + } next_bpp = Tim::Bpp16; uint32_t ret = ff8_externals.credits_open_file(fileName, buffer); @@ -647,14 +705,20 @@ void ff8_upload_vram_triple_triad_1(int16_t *pos_and_size, uint8_t *texture_buff if (texture_buffer == ff8_externals.cardgame_tim_texture_intro) { strncpy(next_texture_name, "cardgame/intro", sizeof(next_texture_name)); + strncpy(next_remastered_texture_name, "cards\\back", sizeof(next_remastered_texture_name)); next_bpp = Tim::Bpp16; } else if (texture_buffer == ff8_externals.cardgame_tim_texture_game) { strncpy(next_texture_name, "cardgame/game", sizeof(next_texture_name)); + strncpy(next_remastered_texture_name, "cards\\board", sizeof(next_remastered_texture_name)); next_bpp = Tim::Bpp16; } + if (is_remastered_hd_textures_disabled("cardgame")) { + *next_remastered_texture_name = '\0'; + } + if (save_textures && *next_texture_name != '\0') { ff8_tim tim = ff8_tim(); @@ -672,6 +736,7 @@ void ff8_upload_vram_triple_triad_2_texture_name(uint8_t *texture_buffer) if (texture_buffer >= ff8_externals.cardgame_tim_texture_cards && texture_buffer < ff8_externals.cardgame_tim_texture_game) { strncpy(next_texture_name, "cardgame/cards", sizeof(next_texture_name)); + strncpy(next_remastered_texture_name, "cards\\cards", sizeof(next_remastered_texture_name)); if (next_pal_data == (uint16_t *)texture_buffer) { if (save_textures) Tim::fromTimData(texture_buffer - 20).saveMultiPaletteGrid(next_texture_name, 28, 4, 128, 2, true); @@ -681,6 +746,7 @@ void ff8_upload_vram_triple_triad_2_texture_name(uint8_t *texture_buffer) else if (texture_buffer >= ff8_externals.cardgame_tim_texture_icons && texture_buffer < ff8_externals.cardgame_tim_texture_font) { strncpy(next_texture_name, "cardgame/icons", sizeof(next_texture_name)); + strncpy(next_remastered_texture_name, "cards\\text", sizeof(next_remastered_texture_name)); if (next_pal_data == (uint16_t *)texture_buffer) { if (save_textures) Tim::fromTimData(texture_buffer - 20).save(next_texture_name, 0, 0, true); @@ -701,6 +767,10 @@ void ff8_upload_vram_triple_triad_2_texture_name(uint8_t *texture_buffer) } next_bpp = tim.bpp(); } + + if (is_remastered_hd_textures_disabled("cardgame")) { + *next_remastered_texture_name = '\0'; + } } void ff8_upload_vram_triple_triad_2_palette(int16_t *pos_and_size, uint8_t *texture_buffer) @@ -751,7 +821,7 @@ void ff8_wm_section_17_set_texture(int texture_id) snprintf(texture_name, sizeof(texture_name), "world/dat/wmset/section17/texture%d", texture_id); - texturePacker.setTexture(texture_name, texture_infos, palette_infos, tex.textureFramePositions.size(), true); + texturePacker.setTexture(texture_name, nullptr, texture_infos, palette_infos, tex.textureFramePositions.size(), true); } uint32_t ff8_wm_section_38_prepare_texture_for_upload(uint8_t *tim_file_data, ff8_tim *tim_infos) @@ -835,7 +905,7 @@ uint32_t ff8_wm_section_38_prepare_texture_for_upload(uint8_t *tim_file_data, ff if (trace_all || trace_vram) ffnx_trace("%s: set animation palette source=(%d, %d)\n", __func__, tex.srcX, tex.srcY); next_texture_count = tex.height; TexturePacker::TextureInfos palette(tex.x, tex.y, 256, tex.height, Tim::Bpp16); - texturePacker.setTexture(nullptr, palette, palette); + texturePacker.setTexture(nullptr, nullptr, palette, palette); if (save_textures) { @@ -887,6 +957,10 @@ Tim ff8_wm_set_texture_name_from_section_position(uint8_t section_number, uint32 snprintf(next_texture_name, MAX_PATH, "world/dat/wmset/section%d/texture%d", section_number, timId); + if (section_number == 42 && timId == 14 && !is_remastered_hd_textures_disabled("world")) { + snprintf(next_remastered_texture_name, MAX_PATH, "field.fs\\field_hd_new\\wmset_014"); + } + Tim tim = Tim::fromTimData(tim_file_data); next_bpp = tim.bpp(); @@ -1272,7 +1346,7 @@ uint32_t ff8_field_read_map_data(char *filename, uint8_t *map_data) for (int i = 0; i < VRAM_PAGE_MIM_MAX_COUNT; ++i) { snprintf(tex_filename, sizeof(tex_filename), "%s_%d", tex_directory, i); const int x = i * TEXTURE_WIDTH_BPP16, y = 256; - texturePacker.setTexture(tex_filename, TexturePacker::TextureInfos(x, y, TEXTURE_WIDTH_BPP16, TEXTURE_HEIGHT, Tim::Bpp16, true), TexturePacker::TextureInfos(), 0); + texturePacker.setTexture(tex_filename, nullptr, TexturePacker::TextureInfos(x, y, TEXTURE_WIDTH_BPP16, TEXTURE_HEIGHT, Tim::Bpp16, true), TexturePacker::TextureInfos(), 0); // Force texture_reload_hack texturePacker.setCurrentAnimationFrame(x, y, -1); } @@ -1361,6 +1435,9 @@ int ff8_field_texture_upload_one(char *image_buffer, char bpp, char a3, int x, i if (chara_one_current_texture < model.texturesData.size()) { next_bpp = Tim::Bpp(bpp); + if (!is_remastered_hd_textures_disabled("field")) { + snprintf(next_remastered_texture_name, MAX_PATH, "field.fs\\field_hd_new\\%s_%d", model.name, chara_one_current_texture); + } snprintf(next_texture_name, MAX_PATH, "field/model/%s_chr/%s-%d", model.isMch ? "main" : "second", model.name, chara_one_current_texture); ++chara_one_current_texture; @@ -1395,7 +1472,7 @@ void ff8_field_effects_upload_vram1(int16_t *pos_and_size, uint8_t *texture_buff ff8_upload_vram(pos_and_size, texture_buffer); // This upload and the next one together - texturePacker.setTexture(texture_name, TexturePacker::TextureInfos(pos_and_size[0], pos_and_size[1], pos_and_size[2], pos_and_size[3] * 2, bpp)); + texturePacker.setTexture(texture_name, nullptr, TexturePacker::TextureInfos(pos_and_size[0], pos_and_size[1], pos_and_size[2], pos_and_size[3] * 2, bpp)); } int battle_get_texture_file_name_index(void *texture_buffer) @@ -1414,6 +1491,7 @@ int16_t ff8_battle_open_and_read_file(int fileId, void *data, int a3, int callba if (trace_all || trace_files) ffnx_trace("%s: %d => %s\n", __func__, fileId, ff8_externals.battle_filenames[fileId]); battle_texture_id = 0; + battle_file_id = fileId; if (stricmp(ff8_externals.battle_filenames[fileId], "B0WAVE.DAT") == 0) { // Fix redundant texture strncpy(battle_texture_name, "battle/A8DEF.TIM", sizeof(battle_texture_name)); @@ -1812,17 +1890,24 @@ void ff8_battle_upload_texture_palette(int16_t *pos_and_size, uint8_t *texture_b // Exceptionally split the texture into 3 pages, because the game will reupload page by page later (via MA8DEF textures) texture_infos = TexturePacker::TextureInfos(tim.imageX(), tim.imageY(), 64, tim.imageHeight(), tim.bpp()); strncpy(next_texture_name, "battle/A8DEF0.TIM", sizeof(next_texture_name)); - texturePacker.setTexture(next_texture_name, texture_infos, palette_infos); + texturePacker.setTexture(next_texture_name, nullptr, texture_infos, palette_infos); texture_infos = TexturePacker::TextureInfos(tim.imageX() + 64, tim.imageY(), 64, tim.imageHeight(), tim.bpp()); strncpy(next_texture_name, "battle/A8DEF1.TIM", sizeof(next_texture_name)); - texturePacker.setTexture(next_texture_name, texture_infos, palette_infos); + texturePacker.setTexture(next_texture_name, nullptr, texture_infos, palette_infos); texture_infos = TexturePacker::TextureInfos(tim.imageX() + 128, tim.imageY(), 64, tim.imageHeight(), tim.bpp()); strncpy(next_texture_name, "battle/A8DEF2.TIM", sizeof(next_texture_name)); - texturePacker.setTexture(next_texture_name, texture_infos, palette_infos); + texturePacker.setTexture(next_texture_name, nullptr, texture_infos, palette_infos); texture_infos = TexturePacker::TextureInfos(); // Bypass next upload vram } else if (battle_texture_id < 0) { strncpy(next_texture_name, battle_texture_name, sizeof(next_texture_name)); } else { + // Remove extension + strncpy(next_texture_name, ff8_externals.battle_filenames[battle_file_id], strlen(ff8_externals.battle_filenames[battle_file_id]) - 4); + next_texture_name[strlen(ff8_externals.battle_filenames[battle_file_id]) - 4] = '\0'; + + if (!is_remastered_hd_textures_disabled("battle")) { + snprintf(next_remastered_texture_name, sizeof(next_remastered_texture_name), "battle.fs\\hd_new\\%s_%d", next_texture_name, battle_texture_id); + } snprintf(next_texture_name, sizeof(next_texture_name), "%s-%d", battle_texture_name, battle_texture_id); ++battle_texture_id; } diff --git a/src/ff8/vram.h b/src/ff8/vram.h index 4600b727..7d3abfa5 100644 --- a/src/ff8/vram.h +++ b/src/ff8/vram.h @@ -23,8 +23,11 @@ #pragma once #include "texture_packer.h" +#include "field/chara_one.h" +#include extern TexturePacker texturePacker; +extern std::unordered_map chara_one_models; void vram_init(); bool ff8_vram_save(const char *fileName, Tim::Bpp bpp); diff --git a/src/ff8/zzz_archive.cpp b/src/ff8/zzz_archive.cpp new file mode 100644 index 00000000..2575fa0b --- /dev/null +++ b/src/ff8/zzz_archive.cpp @@ -0,0 +1,270 @@ +/****************************************************************************/ +// 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) 2022 myst6re // +// Copyright (C) 2022 Julian Xhokaxhiu // +// Copyright (C) 2022 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 "zzz_archive.h" + +#include +#include +#include + +#include "../log.h" + +Zzz::Zzz() : + _f(nullptr) +{ + _fileName[0] = '\0'; +} + +errno_t Zzz::open(const char *fileName) +{ + strncpy(_fileName, fileName, MAX_PATH - 1); + errno_t err = fopen_s(&_f, fileName, "rb"); + + if (err != 0) { + return err; + } + + if (!openHeader()) { + return EILSEQ; + } + + fclose(_f); + + return 0; +} + +bool Zzz::isOpen() const +{ + return !_toc.empty(); +} + +bool Zzz::lookup(const char *fileName, ZzzTocEntry *tocEntry) const +{ + if (trace_all || trace_files) ffnx_trace("Zzz::%s: %s\n", __func__, fileName); + char transformedFileName[ZZZ_FILENAME_MAX_SIZE]; + toWindowsSeparators(fileName, transformedFileName); + + auto it = _toc.find(transformedFileName); + + if (it == _toc.end()) { + if (trace_all || trace_files) ffnx_error("Zzz::%s: file %s not found in %s\n", __func__, transformedFileName, _fileName); + + return false; + } + + if (tocEntry != nullptr) { + *tocEntry = it->second; + } + + return true; +} + +void Zzz::toWindowsSeparators(const char *fileName, char *transformedFileName) +{ + for (int i = 0; i < ZZZ_FILENAME_MAX_SIZE; ++i) { + if (fileName[i] == '/') { + transformedFileName[i] = '\\'; + } else if (fileName[i] == '\0') { + transformedFileName[i] = '\0'; + + return; + } else { + transformedFileName[i] = tolower(fileName[i]); + } + } + + transformedFileName[ZZZ_FILENAME_MAX_SIZE - 1] = '\0'; +} + +bool Zzz::openHeader() +{ + if (!_toc.empty()) + { + return true; + } + + // Optimization: use a buffer to minimize the number of fread calls + constexpr int bufferSize = 0x4000; + char buffer[bufferSize], *cur = buffer, *end = buffer + bufferSize; + + if (fread(cur, bufferSize, 1, _f) != 1) + { + ffnx_error("Zzz::%s: cannot read toc entries\n", __func__); + + return false; + } + + uint32_t fileCount = *(uint32_t *)buffer; + cur += sizeof(uint32_t); + + if (trace_all || trace_files) ffnx_trace("Zzz::%s: found %d files\n", __func__, fileCount); + + for (uint64_t i = 0; i < fileCount; ++i) + { + size_t remainingSize = size_t(end - cur); + + if (remainingSize < ZZZ_FILENAME_MAX_SIZE + 16) { + // Read data again + if (remainingSize > 0) { + memcpy(buffer, cur, remainingSize); + } + if (fread(buffer + remainingSize, bufferSize - remainingSize, 1, _f) != 1) { + ffnx_error("Zzz::%s: cannot read toc entry %d\n", __func__, i); + + return false; + } + cur = buffer; + end = buffer + bufferSize; + } + + /** + * Toc entry format: + * | Offset | Size | Description | + * ------------------------------------- + * | 0 | 4 | Filename length | + * | 4 | varies | Filename | + * | varies | 8 | File pos | + * | varies | 4 | File size | + */ + + ZzzTocEntry tocEntry; + uint32_t fileNameSize = *(uint32_t *)cur; + cur += sizeof(uint32_t); + if (fileNameSize > ZZZ_FILENAME_MAX_SIZE) { + ffnx_error("Zzz::%s: cannot read toc entry %d, fileNameSize > %d\n", __func__, i, ZZZ_FILENAME_MAX_SIZE); + + return false; + } + memcpy(tocEntry.fileName, cur, fileNameSize); + cur += fileNameSize; + tocEntry.fileName[fileNameSize] = '\0'; + tocEntry.fileNameSize = fileNameSize; + tocEntry.filePos = *(uint64_t *)cur; + cur += sizeof(uint64_t); + tocEntry.fileSize = *(uint32_t *)cur; + cur += sizeof(uint32_t); + + _toc[tocEntry.fileName] = tocEntry; + } + + return true; +} + +Zzz::File *Zzz::openFile(const char *fileName) const +{ + int fd = 0; + + if (trace_all || trace_files) ffnx_trace("Zzz::%s: %s\n", __func__, fileName); + + errno_t err = _sopen_s(&fd, _fileName, _O_BINARY, _SH_DENYNO, _S_IREAD); + if (err != 0) + { + return nullptr; + } + + ZzzTocEntry tocEntry; + if (!lookup(fileName, &tocEntry)) + { + _close(fd); + + return nullptr; + } + + if (_lseeki64(fd, tocEntry.filePos, SEEK_SET) != tocEntry.filePos) + { + _close(fd); + + return nullptr; + } + + return new File(tocEntry, fd); +} + +void Zzz::closeFile(File *file) +{ + if (file != nullptr) + { + delete file; + } +} + +Zzz::File::File(const ZzzTocEntry &tocEntry, int fd) : + _tocEntry(tocEntry), _fd(fd), _pos(tocEntry.filePos) +{ +} + +Zzz::File::~File() +{ + if (trace_all) ffnx_trace("Zzz::File::close: %s\n", _tocEntry.fileName); + + _close(_fd); +} + +int64_t Zzz::File::seek(int64_t pos, bx::Whence::Enum whence) +{ + if (trace_all) ffnx_trace("Zzz::File::%s: %u\n", __func__, pos); + + switch (whence) { + case bx::Whence::End: + pos = _tocEntry.fileSize + (pos < 0 ? pos : 0); + break; + case bx::Whence::Current: + pos += relativePos(); + break; + case bx::Whence::Begin: + break; + } + + if (pos < 0) { + return -1; + } + + _pos = _lseeki64(_fd, _tocEntry.filePos + pos, SEEK_SET); + + if (trace_all) ffnx_trace("Zzz::File::%s: new pos=%lld filePos=%lld\n", __func__, _pos, _tocEntry.filePos); + + return relativePos(); +} + +int Zzz::File::read(void *data, unsigned int size) +{ + int64_t pos = relativePos(); + + if (trace_all) ffnx_trace("Zzz::File::%s: size=%u pos=%lld fileSize=%u\n", __func__, size, pos, _tocEntry.fileSize); + + if (pos < 0) { + return -1; // Before the beginning of the file + } + + if (pos >= _tocEntry.fileSize) { + return 0; // End Of File + } + + int r = _read(_fd, data, std::min(size, uint32_t(_tocEntry.fileSize - pos))); + + if (trace_all) ffnx_trace("Zzz::File::%s: read %d bytes\n", __func__, r); + + if (r > 0) { + _pos += r; + } + + return r; +} diff --git a/src/ff8/zzz_archive.h b/src/ff8/zzz_archive.h new file mode 100644 index 00000000..92454f80 --- /dev/null +++ b/src/ff8/zzz_archive.h @@ -0,0 +1,99 @@ +/****************************************************************************/ +// 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) 2022 myst6re // +// Copyright (C) 2022 Julian Xhokaxhiu // +// Copyright (C) 2022 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 +#include +#include +#include +#include + +constexpr size_t ZZZ_FILENAME_MAX_SIZE = 128; + +struct ZzzTocEntry +{ + uint64_t filePos; + uint32_t fileSize; + char fileName[ZZZ_FILENAME_MAX_SIZE]; + uint32_t fileNameSize; +}; + +class Zzz +{ +public: + class File : public bx::ReaderSeekerI + { + public: + int64_t seek(int64_t offset = 0, bx::Whence::Enum whence = bx::Whence::Current) override; + int read(void *data, unsigned int size); + inline virtual int32_t read(void* data, int32_t size, bx::Error* _err) override { + return read(data, size); + } + inline int64_t relativePos() const { + return int64_t(_pos - _tocEntry.filePos); + } + inline uint64_t absolutePos() const { + return _tocEntry.filePos; + } + inline uint32_t size() const { + return _tocEntry.fileSize; + } + inline const char *fileName() const { + return _tocEntry.fileName; + } + inline uint32_t fileNameSize() const { + return _tocEntry.fileNameSize; + } + inline int fd() const { + return _fd; + } + private: + friend class Zzz; + File(const ZzzTocEntry &tocEntry, int fd); + virtual ~File(); + const ZzzTocEntry _tocEntry; + int _fd; + int64_t _pos; + }; + + Zzz(); + ~Zzz() {} + errno_t open(const char *fileName); + bool isOpen() const; + File *openFile(const char *fileName) const; + static void closeFile(File *file); + inline bool fileExists(const char *fileName) const { + return lookup(fileName); + } + inline const char *fileName() const { + return _fileName; + } +private: + friend class File; + bool lookup(const char *fileName, ZzzTocEntry *tocEntry = nullptr) const; + static void toWindowsSeparators(const char *fileName, char *transformedFileName); + bool openHeader(); + + std::map _toc; + char _fileName[MAX_PATH]; + FILE *_f; +}; diff --git a/src/ff8_data.cpp b/src/ff8_data.cpp index 65742fd4..8730526a 100644 --- a/src/ff8_data.cpp +++ b/src/ff8_data.cpp @@ -55,6 +55,10 @@ void ff8_find_externals() ff8_externals.manage_time_engine_sub_569971 = get_relative_call(common_externals.winmain, 0x23); ff8_externals.enable_rdtsc_sub_40AA00 = (int (*)(int))get_relative_call(ff8_externals.manage_time_engine_sub_569971, 0x21); common_externals.get_time = (uint64_t (*)(uint64_t*))get_relative_call(common_externals.winmain, 0x20E); + ff8_externals.archive_path_prefix_menu = (char *)get_absolute_value(ff8_externals.main_entry, 0x3A); + ff8_externals.archive_path_prefix_battle = (char *)get_absolute_value(ff8_externals.main_entry, 0x46); + ff8_externals.archive_path_prefix_field = (char *)get_absolute_value(ff8_externals.main_entry, 0x52); + ff8_externals.archive_path_prefix_world = (char *)get_absolute_value(ff8_externals.main_entry, 0x5E); common_externals.diff_time = (uint64_t (*)(uint64_t*,uint64_t*,uint64_t*))get_relative_call(common_externals.winmain, 0x41E); ff8_externals.init_config = get_relative_call(ff8_externals.main_entry, 0x73); ff8_externals.pubintro_init = get_absolute_value(ff8_externals.main_entry, 0x158); @@ -167,7 +171,21 @@ void ff8_find_externals() common_externals.debug_print2 = get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0x16); ff8_externals.moriya_filesystem_open = get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0x21); ff8_externals.moriya_filesystem_seek = get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0x77); + ff8_externals._lseek = (int (*)(int,long,int))get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0x8B); + ff8_externals._io_fd_number = (unsigned int *)get_absolute_value(uint32_t(ff8_externals._lseek), 0x7); + ff8_externals._io_known_fds = (int *)get_absolute_value(uint32_t(ff8_externals._lseek), 0x1A); + ff8_externals._lock_fhandle = (void (*)(int))get_relative_call(uint32_t(ff8_externals._lseek), 0x2A); + ff8_externals._unlock_fhandle = (int (*)(int))get_relative_call(uint32_t(ff8_externals._lseek), 0x40); + ff8_externals._lseek_lk = (int (*)(int,long,int))get_relative_call(uint32_t(ff8_externals._lseek), 0x38); + ff8_externals._errno = (int* (*)())get_relative_call(uint32_t(ff8_externals._lseek), 0x4D); + ff8_externals.__doserrno = (unsigned long* (*)())get_relative_call(uint32_t(ff8_externals._lseek), 0x58); ff8_externals.moriya_filesystem_read = get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0xB7); + ff8_externals._read = (int (*)(int,void*,unsigned int))get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0xC7); + ff8_externals._read_lk = (unsigned int (*)(int,LPVOID,DWORD))get_relative_call(uint32_t(ff8_externals._read), 0x38); + ff8_externals.open_and_write_to_archive = get_relative_call(ff8_externals.moriya_filesystem_open, 0x3E1); + ff8_externals.write_to_archive = get_relative_call(ff8_externals.open_and_write_to_archive, 0x30); + ff8_externals._write = (int (*)(int,void*,unsigned int))get_relative_call(ff8_externals.write_to_archive, 0x44); + ff8_externals._write_lk = (int (*)(int,LPVOID,DWORD))get_relative_call(uint32_t(ff8_externals._write), 0x38); ff8_externals.moriya_filesystem_close = get_relative_call(uint32_t(ff8_externals.sm_pc_read), 0xDD); ff8_externals.read_or_uncompress_fs_data = get_relative_call(ff8_externals.moriya_filesystem_read, 0x5C); ff8_externals.lzs_uncompress = get_relative_call(ff8_externals.read_or_uncompress_fs_data, 0x1E6); @@ -299,6 +317,18 @@ void ff8_find_externals() ff8_externals.get_key_state = get_relative_call(uint32_t(ff8_externals.ctrl_keyboard_actions), 0x5); ff8_externals.keyboard_state = (byte**)get_absolute_value(ff8_externals.get_key_state, 0x27); + + ff8_externals.sub_533C30 = get_relative_call(ff8_externals.sub_530C30, 0x35A); + ff8_externals.model_vertices_scale_sub_45FE10 = get_relative_call(ff8_externals.sub_533C30, 0x44); + ff8_externals.camera_zoom_dword_1CA92E4 = (uint16_t*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0xA); + ff8_externals.word_1CA92DE = (int16_t*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0x18); + ff8_externals.dword_1CA8A50 = (int*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0x38); + ff8_externals.dword_1CA92F8 = (int*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0x4A); + ff8_externals.dword_1CA8A10 = (int16_t*)(get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0x6F) - 4); + ff8_externals.dword_1CA9290 = (int*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0xAB); + ff8_externals.flt_1CA9234 = (float*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0xB5); + ff8_externals.dword_1CA8A30 = (int*)get_absolute_value(ff8_externals.model_vertices_scale_sub_45FE10, 0x2B5); + ff8_externals.battle_trigger_field = uint32_t(ff8_externals.sub_47CA90) + 0x15; ff8_externals.field_update_seed_level_52B140 = get_relative_call(common_externals.update_field_entities, 0x120); ff8_externals.field_update_rinoa_limit_breaks_52B320 = get_relative_call(common_externals.update_field_entities, 0x183); @@ -340,6 +370,7 @@ void ff8_find_externals() ff8_externals.opcode_movie = common_externals.execute_opcode_table[0x4F]; ff8_externals.opcode_moviesync = common_externals.execute_opcode_table[0x50]; ff8_externals.opcode_spuready = common_externals.execute_opcode_table[0x56]; + ff8_externals.opcode_show = common_externals.execute_opcode_table[0x60]; ff8_externals.opcode_amesw = common_externals.execute_opcode_table[0x64]; ff8_externals.opcode_ames = common_externals.execute_opcode_table[0x65]; ff8_externals.opcode_battle = common_externals.execute_opcode_table[0x69]; @@ -385,6 +416,8 @@ void ff8_find_externals() ff8_externals.drawpoint_messages = get_absolute_value(ff8_externals.opcode_drawpoint, 0xD6); ff8_externals.enable_gf_sub_47E480 = get_relative_call(common_externals.execute_opcode_table[0x129], 0x6E); + ff8_externals.dword_1DCB340 = (void **)get_absolute_value(ff8_externals.opcode_show, 0x2D); + common_externals.debug_print = get_relative_call(common_externals.update_movie_sample, 0x141); ff8_externals._load_texture = get_relative_call(ff8_externals.load_fonts, JP_VERSION ? 0x31B : 0x197); @@ -398,6 +431,8 @@ void ff8_find_externals() common_externals.read_file = get_relative_call(common_externals.load_tex_file, 0x49); common_externals.alloc_read_file = (void* (*)(uint32_t, uint32_t, struct file *))get_relative_call(common_externals.load_tex_file, 0xB3); common_externals.close_file = get_relative_call(common_externals.load_tex_file, 0x15B); + ff8_externals._close = (int(*)(int))get_relative_call(common_externals.close_file, 0x86); + ff8_externals._close_lk = (int (*)(int))get_relative_call(uint32_t(ff8_externals._close), 0x30); common_externals.destroy_tex = (void (*)(tex_header*))get_relative_call(common_externals.load_tex_file, 0x16D); common_externals.destroy_tex_header = get_relative_call((uint32_t)common_externals.destroy_tex, 0x78); common_externals.assert_free = (void* (*)(void*, const char*, uint32_t))get_relative_call(common_externals.destroy_tex_header, 0x21); @@ -526,6 +561,7 @@ void ff8_find_externals() ff8_externals.read_field_data = get_relative_call(ff8_externals.sub_471F70, 0x23A); ff8_externals.upload_mim_file = get_relative_call(ff8_externals.read_field_data, JP_VERSION ? 0x723 : 0x729); ff8_externals.upload_pmp_file = get_relative_call(ff8_externals.read_field_data, JP_VERSION ? 0x80C : 0x812); + ff8_externals.field_filename_concat_extension = get_relative_call(ff8_externals.read_field_data, 0x84); ff8_externals.field_filename = (char *)get_absolute_value(ff8_externals.read_field_data, 0xF0); ff8_externals.field_scripts_init = (int(*)(int,int,int,int))(get_relative_call(ff8_externals.read_field_data, JP_VERSION ? 0xEDC : 0xE49)); @@ -645,6 +681,8 @@ void ff8_find_externals() ff8_externals.sfx_is_playing = get_relative_call(ff8_externals.sfx_current_channel_is_playing - 0x88, 0x0); ff8_externals.sfx_set_panning = get_relative_call(common_externals.play_sfx_on_channel, 0x115); ff8_externals.sfx_audio_fmt = (ff8_audio_fmt **)get_absolute_value(common_externals.sfx_init, 0x21B); + ff8_externals.sfx_initialize_audio_data = get_relative_call(common_externals.sfx_init, 0x225); + ff8_externals._filelength = (__int32 (*)(int))get_relative_call(ff8_externals.sfx_initialize_audio_data, 0x122); ff8_externals.sfx_sound_count = (uint16_t *)get_absolute_value(common_externals.sfx_init, 0x22C); // Search DirectSoundBuffer initialization @@ -920,6 +958,9 @@ void ff8_find_externals() ff8_externals.battle_magic_id = (int*)get_absolute_value(ff8_externals.battle_read_effect_sub_50AF20, 0x3E); ff8_externals.sub_571870 = get_relative_call(ff8_externals.battle_read_effect_sub_50AF20, 0x63); ff8_externals.func_off_battle_effect_textures_50AF93 = (DWORD*)get_absolute_value(ff8_externals.battle_read_effect_sub_50AF20, 0x6B); + ff8_externals.get_battle_effect_buffer_sub_571B50 = get_relative_call(ff8_externals.battle_read_effect_sub_50AF20, 0x75); + ff8_externals.get_battle_effect_buffer_size_sub_571B60 = ff8_externals.get_battle_effect_buffer_sub_571B50 + 0x10; + ff8_externals.init_battle_effect_buffer_sub_571B80 = ff8_externals.get_battle_effect_buffer_size_sub_571B60 + 0x20; ff8_externals.sub_6C3640 = get_relative_call(ff8_externals.func_off_battle_effects_C81774[FF8BattleEffect::Quezacotl], 0x5); ff8_externals.sub_6C3760 = get_absolute_value(ff8_externals.sub_6C3640, 0x8B); diff --git a/src/ff8_opengl.cpp b/src/ff8_opengl.cpp index bc6c0fad..4547a147 100644 --- a/src/ff8_opengl.cpp +++ b/src/ff8_opengl.cpp @@ -36,6 +36,7 @@ #include "ff8/file.h" #include "ff8/vram.h" #include "ff8/save_data.h" +#include "ff8/remaster.h" #include "metadata.h" #include "achievement.h" #include "widescreen.h" @@ -179,16 +180,40 @@ struct ff8_tex_header *ff8_load_tex_file(struct ff8_file_context* file_context, } } - ret->file.pc_name = (char*)external_malloc(1024); + ret->file.pc_name = (char*)external_calloc(1024, sizeof(char)); - len = _snprintf(ret->file.pc_name, 1024, "%s", &filename[7]); + if (ret->file.pc_name != nullptr) { + len = _snprintf(ret->file.pc_name, 511, "%s", &filename[7]); - for(i = 0; i < len; i++) - { - if(ret->file.pc_name[i] == '.') + for(i = 0; i < len; i++) { - if(!_strnicmp(&ret->file.pc_name[i], ".TEX", 4)) ret->file.pc_name[i] = 0; - else ret->file.pc_name[i] = '_'; + if(ret->file.pc_name[i] == '.') + { + if(!_strnicmp(&ret->file.pc_name[i], ".TEX", 4)) ret->file.pc_name[i] = 0; + else ret->file.pc_name[i] = '_'; + } + } + + // Remastered alternative name + char langPath[16] = {}, suffix[ZZZ_FILENAME_MAX_SIZE] = {}, remasterFileName[ZZZ_FILENAME_MAX_SIZE] = {}; + concat_lang_str(langPath); + _snprintf(suffix, sizeof(suffix), "%s", filename + 7 + strlen(ff8_externals.archive_path_prefix)); + + // Disable if ff8_high_res_font is 0 or 1 + font + if (!is_remastered_hd_textures_disabled("menu") && (ff8_high_res_font == -1 || ff8_high_res_font == 2 || (!strstr(suffix, "hires\\sysevn") && !strstr(suffix, "hires\\sysodd") && !strstr(suffix, "hires\\sysfld") && !strstr(suffix, "hires\\sysfld") && !strstr(suffix, "font8")))) { + // Non-paletted + lang path + _snprintf(remasterFileName, sizeof(remasterFileName), "textures\\%s_%s.png", suffix, langPath); + if (remastered_edition && g_FF8ZzzArchiveMain.fileExists(remasterFileName)) { + _snprintf(ret->file.pc_name + 512, 511, "%s_%s", suffix, langPath); + } else { + // Paletted + lang path + _snprintf(remasterFileName, sizeof(remasterFileName), "textures\\%s\\%s\\0.png", suffix, langPath); + if (remastered_edition && g_FF8ZzzArchiveMain.fileExists(remasterFileName)) { + _snprintf(ret->file.pc_name + 512, 511, "%s\\%s", suffix, langPath); + } else { + strncpy(ret->file.pc_name + 512, suffix, strlen(suffix)); + } + } } } @@ -1552,6 +1577,14 @@ void ff8_init_hooks(struct game_obj *_game_object) replace_call(ff8_externals.moriya_filesystem_open + 0x83C, ff8_fs_archive_search_filename_sub_archive); replace_function(ff8_externals._open, ff8_open); replace_function(ff8_externals.fopen, ff8_fopen); + if (remastered_edition) { + replace_function(uint32_t(ff8_externals._lseek), ff8_lseek); + replace_function(uint32_t(ff8_externals._read), ff8_read); + replace_function(uint32_t(ff8_externals._write), ff8_write); + replace_function(uint32_t(ff8_externals._close), ff8_close); + replace_function(uint32_t(ff8_externals._filelength), ff8_filelength); + replace_function(ff8_externals.field_filename_concat_extension, ff8_fs_archive_field_concat_extension); + } replace_call(ff8_externals.moriya_filesystem_close + 0x1F, ff8_fs_archive_free_file_container_sub_archive); ff8_read_file = (uint32_t(*)(uint32_t, void *, struct ff8_file *))common_externals.read_file; @@ -1758,7 +1791,7 @@ void ff8_init_hooks(struct game_obj *_game_object) replace_call(ff8_externals.load_credits_image + 0x164, credits_controller_music_play); replace_call(ff8_externals.load_credits_image + 0x305, credits_controller_input_call); - if (!steam_edition) { + if (!steam_edition && !remastered_edition) { // Look again with the DataDrive specified in the register replace_call(ff8_externals.get_disk_number + 0x6E, ff8_retry_configured_drive); replace_call(ff8_externals.cdcheck_sub_52F9E0 + 0x15E, ff8_retry_configured_drive); @@ -1897,6 +1930,14 @@ void ff8_init_hooks(struct game_obj *_game_object) // Extend field data size patch_code_dword(ff8_externals.read_field_data + (JP_VERSION ? 0xF64 : 0xED1), uint32_t(extended_memory) + 0x5F0000); patch_code_dword(ff8_externals.read_field_data + (JP_VERSION ? 0xF6B : 0xED8), uint32_t(extended_memory) + 0x600000); + + // Relocate effect buffer + patch_code_dword(ff8_externals.sub_571870 + 0x3, 0x380000 / 4); // Patch size + patch_code_dword(ff8_externals.sub_571870 + 0xA, uint32_t(extended_memory) + 0x200000); // Patch offset + patch_code_dword(ff8_externals.get_battle_effect_buffer_sub_571B50 + 0x1, uint32_t(extended_memory) + 0x200000); // Patch offset + patch_code_dword(ff8_externals.get_battle_effect_buffer_size_sub_571B60 + 0x1, 0x380000); // Patch size + patch_code_dword(ff8_externals.init_battle_effect_buffer_sub_571B80 + 0x5, 0x380000); // Patch size + patch_code_dword(ff8_externals.init_battle_effect_buffer_sub_571B80 + 0x13, uint32_t(extended_memory) + 0x200000); // Patch offset } else { ffnx_error("%s: cannot allocate extended_memory\n", __func__); } diff --git a/src/game_cfg.cpp b/src/game_cfg.cpp index 3fde8cc9..623cd603 100644 --- a/src/game_cfg.cpp +++ b/src/game_cfg.cpp @@ -49,13 +49,18 @@ void ff8_set_game_paths(int install_options, char *_app_path, const char *_dataD normalize_path_win(_app_path); } - if (!steam_edition && !data_drive.empty()) + if (!steam_edition && !remastered_edition && !data_drive.empty()) { ffnx_info("Overriding DataDrive with %s\n", data_drive.c_str()); _dataDrive = data_drive.c_str(); } ff8_externals.set_game_paths(install_options, _app_path, _dataDrive); + + if (remastered_edition) + { + strncpy(ff8_externals.music_path, "data\\music\\stream\\", sizeof("data\\music\\stream\\")); + } } bool ff8_reg_set_graphics(uint32_t graphics) @@ -77,16 +82,16 @@ bool ff8_reg_set_graphics(uint32_t graphics) int ff8_reg_get_midiguid(LPDWORD midi_guid) { - int ret = ff8_externals.reg_get_midiguid((LPBYTE)midi_guid); + int ret = remastered_edition ? 0 : ff8_externals.reg_get_midiguid((LPBYTE)midi_guid); LPDWORD default_midi_guid[4] = {0, 0, 0, 0}; - if (memcmp(midi_guid, default_midi_guid, 16) == 0) { + if (remastered_edition || memcmp(midi_guid, default_midi_guid, 16) == 0) { ffnx_info("MIDI GUID is zero, force to Microsoft Synthesizer\n"); // Use default Microsoft synthesizer, instead of starting FF8Config.exe uint32_t buf[4] = {0x58C2B4D0, 0x11D146E7, 0xA000AC89, 0x294105C9}; memcpy(midi_guid, buf, 16); - if (!steam_edition) { + if (!steam_edition && !remastered_edition) { ff8_externals.reg_set_midiguid((uint8_t *)buf); ff8_reg_set_graphics(0x100021); // High res by default } @@ -99,7 +104,7 @@ int ff8_reg_get_midiguid(LPDWORD midi_guid) uint32_t ff8_reg_get_graphics() { - uint32_t ret = ff8_externals.reg_get_graphics(); + uint32_t ret = remastered_edition ? 0x00100021 : ff8_externals.reg_get_graphics(); ret |= 0x00100000; // Force this flag to prevent graphical glitches @@ -112,6 +117,42 @@ uint32_t ff8_reg_get_graphics() return ret; } +void ff8_reg_get_app_path(CHAR *lpData, DWORD cbData) +{ + char buf[MAX_PATH]{ 0 }; + GetCurrentDirectory(sizeof(buf), buf); + strcat(buf, R"(\)"); + strcpy(lpData, buf); +} + +void ff8_reg_get_data_drive(CHAR *lpData, DWORD cbData) +{ + strcpy(lpData, "CD:"); +} + +int ff8_reg_get_installoptions() +{ + return 0x000000ff; +} + +int ff8_reg_get_guid(LPBYTE lpData) +{ + LPSTR buf[16]{ 0 }; + memcpy(lpData, buf, 16); + + return 1; +} + +int ff8_reg_get_soundoptions() +{ + return 0x00000000; +} + +int ff8_reg_get_midioptions() +{ + return 0x00000001; +} + void game_cfg_init() { if (ff8) @@ -119,5 +160,16 @@ void game_cfg_init() replace_call(ff8_externals.init_config + 0x3E, ff8_set_game_paths); replace_call(ff8_externals.init_config + 0x48, ff8_reg_get_midiguid); replace_call(ff8_externals.init_config + 0x16B, ff8_reg_get_graphics); + + if (remastered_edition) + { + replace_call(ff8_externals.init_config + 0x15, ff8_reg_get_app_path); + replace_call(ff8_externals.init_config + 0x21, ff8_reg_get_data_drive); + replace_call(ff8_externals.init_config + 0x26, ff8_reg_get_installoptions); + replace_call(ff8_externals.pubintro_init + 0x14, ff8_reg_get_guid); // Graphics + replace_call(ff8_externals.pubintro_init + 0x28, ff8_reg_get_guid); // Sound + replace_call(ff8_externals.pubintro_init + 0x1E, ff8_reg_get_soundoptions); + replace_call(ff8_externals.pubintro_init + 0x32, ff8_reg_get_midioptions); + } } } diff --git a/src/globals.h b/src/globals.h index 6404817d..82c9de5e 100644 --- a/src/globals.h +++ b/src/globals.h @@ -52,6 +52,7 @@ extern uint32_t version; extern uint32_t steam_edition; extern uint32_t ff7_steam_rerelease_edition; extern uint32_t steam_stock_launcher; +extern uint32_t remastered_edition; extern uint32_t estore_edition; extern uint32_t ff7_2026_rerelease; extern uint32_t ff7_japanese_edition; diff --git a/src/image/image.cpp b/src/image/image.cpp index d1d11094..a611b7d0 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -24,6 +24,7 @@ #include #include "image.h" +#include "../ff8/remaster.h" #include "../common.h" #include "../renderer.h" #include "log.h" @@ -52,19 +53,34 @@ void read_png_file(png_structp png_ptr, png_bytep data, size_t size) bimg::ImageContainer *loadPng(bx::AllocatorI *allocator, const char *filename, bimg::TextureFormat::Enum targetFormat) { - bimg::ImageContainer *ret; - bx::FileReader reader; - bx::Error err; + bimg::ImageContainer *ret = nullptr; - if (!bx::open(&reader, filename, &err) || !err.isOk()) { - return nullptr; - } + if (remastered_edition && strncmp(filename, "zzz://", 6) == 0) { + Zzz::File *zzzFile = g_FF8ZzzArchiveMain.openFile(filename + 6); + + if (zzzFile == nullptr) { + return nullptr; + } + + if (trace_all || trace_loaders) ffnx_trace("%s: %s\n", __func__, filename); - if (trace_all || trace_loaders) ffnx_trace("%s: %s\n", __func__, filename); + ret = loadPng(allocator, zzzFile, targetFormat); - ret = loadPng(allocator, &reader, targetFormat); + Zzz::closeFile(zzzFile); + } else { + bx::FileReader reader; + bx::Error err; + + if (!bx::open(&reader, filename, &err) || !err.isOk()) { + return nullptr; + } - bx::close(&reader); + if (trace_all || trace_loaders) ffnx_trace("%s: %s\n", __func__, filename); + + ret = loadPng(allocator, &reader, targetFormat); + + bx::close(&reader); + } return ret; } diff --git a/src/movies.cpp b/src/movies.cpp index 3a524302..582693ca 100644 --- a/src/movies.cpp +++ b/src/movies.cpp @@ -29,6 +29,7 @@ #include "achievement.h" #include "utils.h" #include "ff8/movies.h" +#include "ff8/remaster.h" enum MovieAudioLayers { MUSIC = 0, @@ -207,47 +208,67 @@ void ff8_prepare_movie(uint8_t disc, uint32_t movie) if(disc != 4) { _snprintf(camName, sizeof(camName), "data/movies/disc%02i_%02i.cam", disc, movie); + bool camdata_read = false; - if (redirect_path_with_override(camName, newCamName, sizeof(newCamName)) != 0) { - _snprintf(newCamName, sizeof(newCamName), "%s/%s", ff8_externals.app_path, camName); - } + if (remastered_edition) + { + Zzz::File *f = g_FF8ZzzArchiveOther.openFile(camName); - FILE *camFile = fopen(newCamName, "rb"); + if (f != nullptr) + { + f->read(&ff8_movie_cam_buffer, f->size()); + Zzz::closeFile(f); - if(!camFile) - { - if (steam_edition) + camdata_read = true; + } + else { - ffnx_error("could not load camera data from %s\n", newCamName); - return; + ffnx_warning("could not load camera data from %s in other.zzz\n", camName); } + } - // Try to open the cam files using the FF8 2000 PAK files - const pak_pointers_entry &pak_pointer = ff8_externals.disc_pak_offsets[disc][movie]; - - char filename[MAX_PATH] = {}; - strcpy(filename, ff8_externals.data_drive_path); - strcat(filename, ff8_externals.disc_pak_filenames[disc]); + if (!camdata_read) { + if (redirect_path_with_override(camName, newCamName, sizeof(newCamName)) != 0) { + _snprintf(newCamName, sizeof(newCamName), "%s/%s", ff8_externals.app_path, camName); + } - FILE *camFile = fopen(filename, "rb"); + FILE *camFile = fopen(newCamName, "rb"); if (!camFile) { - ffnx_error("could not load camera data from %s\n", filename); - return; + if (steam_edition) + { + ffnx_error("could not load camera data from %s\n", newCamName); + return; + } + + // Try to open the cam files using the FF8 2000 PAK files + const pak_pointers_entry &pak_pointer = ff8_externals.disc_pak_offsets[disc][movie]; + + char filename[MAX_PATH] = {}; + strcpy(filename, ff8_externals.data_drive_path); + strcat(filename, ff8_externals.disc_pak_filenames[disc]); + + FILE *camFile = fopen(filename, "rb"); + + if (!camFile) + { + ffnx_error("could not load camera data from %s\n", filename); + return; + } + + fseek(camFile, pak_pointer.cam_offset, SEEK_SET); + fread(&ff8_movie_cam_buffer, 1, pak_pointer.bik_offset - pak_pointer.cam_offset, camFile); + fclose(camFile); + } + else + { + fseek(camFile, 0, SEEK_END); + long camFileSize = ftell(camFile); + rewind(camFile); + fread(&ff8_movie_cam_buffer, 1, camFileSize, camFile); + fclose(camFile); } - - fseek(camFile, pak_pointer.cam_offset, SEEK_SET); - fread(&ff8_movie_cam_buffer, 1, pak_pointer.bik_offset - pak_pointer.cam_offset, camFile); - fclose(camFile); - } - else - { - fseek(camFile, 0, SEEK_END); - long camFileSize = ftell(camFile); - rewind(camFile); - fread(&ff8_movie_cam_buffer, 1, camFileSize, camFile); - fclose(camFile); } ff8_externals.movie_object->movie_intro_pak = false; @@ -269,13 +290,29 @@ void ff8_prepare_movie(uint8_t disc, uint32_t movie) } } - if (!steam_edition && !fileExists(newFmvName)) { - void *opaque = ff8_bink_open(disc, movie); + ffmpeg_release_movie_objects(); + + if (!fileExists(newFmvName)) { + if (remastered_edition) { + // Force 'avi' extension + _snprintf(fmvName, sizeof(fmvName), "data/movies/disc%02i_%02ih.avi", disc, movie); + void *opaque = ff8_zzz_open(fmvName); - if (opaque != nullptr) { - ff8_movie_frames = ffmpeg_prepare_movie_from_io(newFmvName, opaque, ff8_bink_read, ff8_bink_seek, ff8_bink_close); + if (opaque != nullptr) { + ff8_movie_frames = ffmpeg_prepare_movie_from_io(fmvName, opaque, ff8_zzz_read, ff8_zzz_seek, ff8_zzz_close); - return; + return; + } + } else if (!steam_edition) { + // Name is mostly for logs + _snprintf(fmvName, sizeof(fmvName), "disc%02i_%02ih.bik", disc, movie); + void *opaque = ff8_bink_open(disc, movie); + + if (opaque != nullptr) { + ff8_movie_frames = ffmpeg_prepare_movie_from_io(fmvName, opaque, ff8_bink_read, ff8_bink_seek, ff8_bink_close); + + return; + } } } diff --git a/src/music.cpp b/src/music.cpp index 96de3915..0bd42097 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -25,6 +25,8 @@ #include "audio.h" #include "music.h" #include "patch.h" +#include "ff8/remaster.h" +#include "audio/vgmstream/zzzstreamfile.h" #include "ff8/engine.h" @@ -295,6 +297,31 @@ bool play_music(const char* music_name, uint32_t music_id, int channel, NxAudioE strcpy(options.format, "wav"); playing = nxAudioEngine.playMusic(wav_fullpath, music_id, channel, options); } + + if (!playing && remastered_edition) + { + char file_name[MAX_PATH] = {}; + + if (wav_fullpath != nullptr) + { + snprintf(file_name, sizeof(file_name), "zzz://%s", wav_fullpath); + options.useNameAsFullPath = true; + strcpy(options.format, "wav"); + playing = nxAudioEngine.playMusic(file_name, music_id, channel, options); + } + else + { + bool old_ff8_external_music_force_original_filenames = ff8_external_music_force_original_filenames; + ff8_external_music_force_original_filenames = true; + music_name = ff8_midi_name(music_id); + ff8_external_music_force_original_filenames = old_ff8_external_music_force_original_filenames; + + snprintf(file_name, sizeof(file_name), "zzz://data\\music\\dmusic\\ogg\\%s.ogg", music_name); + options.useNameAsFullPath = true; + strcpy(options.format, "ogg"); + playing = nxAudioEngine.playMusic(file_name, music_id, channel, options); + } + } } else { diff --git a/src/saveload.cpp b/src/saveload.cpp index 69fb78aa..0a8f1141 100644 --- a/src/saveload.cpp +++ b/src/saveload.cpp @@ -146,6 +146,10 @@ uint32_t load_normal_texture(const void* data, uint32_t dataSize, const char* na { _snprintf(filename, sizeof(filename), "%s/%s/%s.%s", basedir, tex_path.c_str(), name, mod_ext[idx].c_str()); } + else if ((palette_index & 0xC0000000) == 0xC0000000) // Remastered + { + _snprintf(filename, sizeof(filename), "%s/%s/%s/%u.%s", basedir, tex_path.c_str(), name, palette_index & 0xFFFF, mod_ext[idx].c_str()); + } else if (palette_index & 0x40000000) { _snprintf(filename, sizeof(filename), "%s/%s/%s_%u_%u.%s", basedir, tex_path.c_str(), name, (palette_index & 0x7FFF), ((palette_index & 0x3FFFFFFF) >> 15) & 0x7FFF, mod_ext[idx].c_str()); diff --git a/src/steam.cpp b/src/steam.cpp new file mode 100644 index 00000000..e5665dcf --- /dev/null +++ b/src/steam.cpp @@ -0,0 +1,217 @@ +#include "steam.h" +#include "utils.h" +#include "log.h" + +#include +#include +#include + +HMODULE hDll = nullptr; +FARPROC procRunCallbacks = nullptr; +FARPROC procRegisterCallback = nullptr; +FARPROC procUnregisterCallback = nullptr; +FARPROC procSteamUser = nullptr; +FARPROC procSteamUtils = nullptr; +FARPROC procUserStats = nullptr; + +typedef bool(*LPRESTARTAPPIFNECESSARY)(uint32); +typedef bool(*LPINIT)(); +typedef void(*LPRUNCALLBACKS)(); +typedef void(*LPSHUTDOWN)(); +typedef void(*LPREGISTERCALLBACK)(class CCallbackBase *pCallback, int iCallback); +typedef void(*LPUNREGISTERCALLBACK)(class CCallbackBase *pCallback); +typedef ISteamUser*(*LPUSER)(); +typedef ISteamUtils*(*LPUTILS)(); +typedef ISteamUserStats*(*LPUSERSTATS)(); + +bool load_library() +{ + if (hDll != nullptr) { + return true; + } + + char lib_path[MAX_PATH] = {}; + + strncpy(lib_path, "ffnx_steam_api.dll", sizeof(lib_path)); + + if (!fileExists(lib_path)) { + strncpy(lib_path, "steam_api.dll", sizeof(lib_path)); + } + + bool is_genuine_steam_api = isFileSigned(lib_path); + if (!is_genuine_steam_api) is_genuine_steam_api = sha1_file(lib_path) == "03bd9f3e352553a0af41f5fe006f6249a168c243"; + if (!is_genuine_steam_api) + { + char message[256] = {}; + snprintf(message, sizeof(message), "Invalid %s detected. Please ensure your FFNx installation is not corrupted or tampered by unauthorized software.\n", lib_path); + ffnx_unexpected(message); + MessageBoxA(nullptr, message, "Error", MB_ICONERROR | MB_OK); + return false; + } + + hDll = LoadLibraryA(lib_path); + + if (hDll != nullptr) { + return true; + } + + ffnx_error("%s: cannot load library %s\n", __func__, lib_path); + + return false; +} + +bool FFNx_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID) +{ + if (!load_library()) { + return false; + } + + FARPROC proc = GetProcAddress(hDll, "SteamAPI_RestartAppIfNecessary"); + if (proc == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return false; + } + + return LPRESTARTAPPIFNECESSARY(proc)(unOwnAppID); +} + +bool FFNx_SteamAPI_Init() +{ + if (!load_library()) { + return false; + } + + FARPROC proc = GetProcAddress(hDll, "SteamAPI_Init"); + if (proc == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return false; + } + + return LPINIT(proc)(); +} + +void FFNx_SteamAPI_RunCallbacks() +{ + if (!load_library()) { + return; + } + + if (procRunCallbacks == nullptr) { + procRunCallbacks = GetProcAddress(hDll, "SteamAPI_RunCallbacks"); + if (procRunCallbacks == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return; + } + } + + return LPRUNCALLBACKS(procRunCallbacks)(); +} + +void FFNx_SteamAPI_Shutdown() +{ + if (!load_library()) { + return; + } + + FARPROC proc = GetProcAddress(hDll, "SteamAPI_Shutdown"); + if (proc == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return; + } + + return LPSHUTDOWN(proc)(); +} + +void FFNx_SteamAPI_RegisterCallback(class CCallbackBase *pCallback, int iCallback) +{ + if (!load_library()) { + return; + } + + if (procRegisterCallback == nullptr) { + procRegisterCallback = GetProcAddress(hDll, "SteamAPI_RegisterCallback"); + if (procRegisterCallback == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return; + } + } + + return LPREGISTERCALLBACK(procRegisterCallback)(pCallback, iCallback); +} + +void FFNx_SteamAPI_UnregisterCallback(class CCallbackBase *pCallback) +{ + if (!load_library()) { + return; + } + + if (procUnregisterCallback == nullptr) { + procUnregisterCallback = GetProcAddress(hDll, "SteamAPI_UnregisterCallback"); + if (procUnregisterCallback == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return; + } + } + + return LPUNREGISTERCALLBACK(procUnregisterCallback)(pCallback); +} + +ISteamUser *FFNx_SteamUser() +{ + if (!load_library()) { + return nullptr; + } + + if (procSteamUser == nullptr) { + procSteamUser = GetProcAddress(hDll, "SteamUser"); + if (procSteamUser == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return nullptr; + } + } + + return LPUSER(procSteamUser)(); +} + +ISteamUtils *FFNx_SteamUtils() +{ + if (!load_library()) { + return nullptr; + } + + if (procSteamUtils == nullptr) { + procSteamUtils = GetProcAddress(hDll, "SteamUtils"); + if (procSteamUtils == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return nullptr; + } + } + + return LPUTILS(procSteamUtils)(); +} + +ISteamUserStats *FFNx_SteamUserStats() +{ + if (!load_library()) { + return nullptr; + } + + if (procUserStats == nullptr) { + procUserStats = GetProcAddress(hDll, "SteamUserStats"); + if (procUserStats == nullptr) { + ffnx_error("%s: Function not found in Steam lib\n", __func__); + + return nullptr; + } + } + + return LPUSERSTATS(procUserStats)(); +} diff --git a/src/steam.h b/src/steam.h new file mode 100644 index 00000000..ad4d4611 --- /dev/null +++ b/src/steam.h @@ -0,0 +1,112 @@ +/****************************************************************************/ +// 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 + +#include + +bool FFNx_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID); +#define SteamAPI_RestartAppIfNecessary FFNx_SteamAPI_RestartAppIfNecessary +bool FFNx_SteamAPI_Init(); +#define SteamAPI_Init FFNx_SteamAPI_Init +void FFNx_SteamAPI_RunCallbacks(); +#define SteamAPI_RunCallbacks FFNx_SteamAPI_RunCallbacks +void FFNx_SteamAPI_Shutdown(); +#define SteamAPI_Shutdown FFNx_SteamAPI_Shutdown +void FFNx_SteamAPI_RegisterCallback(class CCallbackBase *pCallback, int iCallback); +void FFNx_SteamAPI_UnregisterCallback(class CCallbackBase *pCallback); + +ISteamUser *FFNx_SteamUser(); +#define SteamUser FFNx_SteamUser +ISteamUtils *FFNx_SteamUtils(); +#define SteamUtils FFNx_SteamUtils +ISteamUserStats *FFNx_SteamUserStats(); +#define SteamUserStats FFNx_SteamUserStats + +//----------------------------------------------------------------------------- +// Purpose: maps a steam callback to a class member function +// template params: T = local class, P = parameter struct +//----------------------------------------------------------------------------- +template< class T, class P > +class FFNxCCallback : protected CCallbackBase +{ +public: + typedef void (T::*func_t)( P* ); + + // If you can't support constructing a callback with the correct parameters + // then uncomment the empty constructor below and manually call + // ::Register() for your object + // Or, just call the regular constructor with (NULL, NULL) + // FFNxCCallback() {} + + // constructor for initializing this object in owner's constructor + FFNxCCallback( T *pObj, func_t func ) : m_pObj( pObj ), m_Func( func ) + { + if ( pObj && func ) + Register( pObj, func ); + } + + ~FFNxCCallback() + { + if ( m_nCallbackFlags & k_ECallbackFlagsRegistered ) + Unregister(); + } + + // manual registration of the callback + void Register( T *pObj, func_t func ) + { + if ( !pObj || !func ) + return; + + if ( m_nCallbackFlags & k_ECallbackFlagsRegistered ) + Unregister(); + + m_pObj = pObj; + m_Func = func; + // SteamAPI_RegisterCallback sets k_ECallbackFlagsRegistered + FFNx_SteamAPI_RegisterCallback( this, P::k_iCallback ); + } + + void Unregister() + { + // SteamAPI_UnregisterCallback removes k_ECallbackFlagsRegistered + FFNx_SteamAPI_UnregisterCallback( this ); + } +protected: + virtual void Run( void *pvParam ) + { + (m_pObj->*m_Func)( (P *)pvParam ); + } + virtual void Run( void *pvParam, bool, SteamAPICall_t ) + { + (m_pObj->*m_Func)( (P *)pvParam ); + } + int GetCallbackSizeBytes() + { + return sizeof( P ); + } + + T *m_pObj; + func_t m_Func; +}; + +// utility macro for declaring the function and callback object together +#undef STEAM_CALLBACK +#define STEAM_CALLBACK( thisclass, func, param, var ) FFNxCCallback< thisclass, param > var; void func( param *pParam ) diff --git a/src/world.cpp b/src/world.cpp index a1c1ff10..cc1a918e 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -56,7 +56,7 @@ void world_init() { if (ff8) { - cameraZoom = (WORD*)get_absolute_value(ff8_externals.sub_4023D0, 0x10); + cameraZoom = ff8_externals.camera_zoom_dword_1CA92E4; playerPosX = (DWORD*)get_absolute_value(ff8_externals.sub_53BB90, 0x4C); playerPosZ = playerPosX+1; playerPosY = playerPosX+2;