From dd81fd44aaaab43fe5207fe4d58f26723d1754f9 Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Sun, 25 Jan 2026 15:03:49 +0100 Subject: [PATCH 1/7] Allow fedata to be extracted separately --- NFS3/FEDATA/FedataFile.h | 1 + NFS3/NFS3Loader.cpp | 29 ++++++++++++++++++++++++++++- NFS3/NFS3Loader.h | 2 ++ Shared/VIV/VivArchive.cpp | 38 +++++++++++++++++++++++++++++++++++++- Shared/VIV/VivArchive.h | 1 + 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/NFS3/FEDATA/FedataFile.h b/NFS3/FEDATA/FedataFile.h index 552775b..ceab946 100644 --- a/NFS3/FEDATA/FedataFile.h +++ b/NFS3/FEDATA/FedataFile.h @@ -1,6 +1,7 @@ #pragma once #include "../../Common/IRawData.h" +#include "../../Shared/VIV/VivArchive.h" namespace LibOpenNFS::NFS3 { diff --git a/NFS3/NFS3Loader.cpp b/NFS3/NFS3Loader.cpp index cef1bee..36af340 100644 --- a/NFS3/NFS3Loader.cpp +++ b/NFS3/NFS3Loader.cpp @@ -29,7 +29,7 @@ namespace LibOpenNFS::NFS3 { Car::PhysicsData carPhysicsData; - if (std::filesystem::exists(carOutPath)) { + if (std::filesystem::exists(fcePath.str()) && std::filesystem::exists(fedataPath.str()) && std::filesystem::exists(carpPath.str())) { LogInfo("VIV has already been extracted to %s, skipping", carOutPath.c_str()); } else { ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); @@ -98,6 +98,33 @@ namespace LibOpenNFS::NFS3 { return track; } + FedataFile Loader::LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath) { + LogInfo("Loading NFS3 car menu data from %s into %s", carBasePath.c_str(), carOutPath.c_str()); + + std::filesystem::path p(carBasePath); + std::string carName = p.filename().string(); + std::string const fedataFileName = "fedata.eng"; + std::stringstream vivPath, fcePath, fedataPath, carpPath; + vivPath << carBasePath << "/car.viv"; + fedataPath << carOutPath << "/" << fedataFileName; + + Shared::VivArchive vivFile; + FedataFile fedataFile; + + if (std::filesystem::exists(fedataPath.str())) { + LogInfo("Fedata file has already been extracted to %s, skipping", carOutPath.c_str()); + } else { + ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); + ASSERT(Shared::VivArchive::ExtractFile(carOutPath, vivFile, fedataFileName), + "Could not extract fedata file from VIV file: " << vivPath.str() << "to: " << carOutPath); + } + if (!FedataFile::Load(fedataPath.str(), fedataFile)) { + LogWarning("Could not load FeData file: %s", fedataPath.str().c_str()); + } + + return fedataFile; + } + Car::MetaData Loader::_ParseAssetData(FceFile const &fceFile, FedataFile const &fedataFile) { LogInfo("Parsing FCE File into ONFS Structures"); diff --git a/NFS3/NFS3Loader.h b/NFS3/NFS3Loader.h index 43ac6f0..33aa468 100644 --- a/NFS3/NFS3Loader.h +++ b/NFS3/NFS3Loader.h @@ -30,6 +30,8 @@ namespace LibOpenNFS::NFS3 { static Car LoadCar(std::string const &carBasePath, std::string const &carOutPath); static Track LoadTrack(std::string const &trackBasePath); + static FedataFile LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath); + private: static Car::MetaData _ParseAssetData(FceFile const &fceFile, FedataFile const &fedataFile); static Car::PhysicsData _ParsePhysicsData(Shared::CarpFile const &carpFile); diff --git a/Shared/VIV/VivArchive.cpp b/Shared/VIV/VivArchive.cpp index 8a0fc8f..d97766f 100644 --- a/Shared/VIV/VivArchive.cpp +++ b/Shared/VIV/VivArchive.cpp @@ -24,7 +24,8 @@ namespace LibOpenNFS::Shared { } bool VivArchive::Extract(std::string const &outPath, VivArchive &vivFile) { - std::filesystem::create_directories(outPath); + if (!std::filesystem::exists(outPath)) + std::filesystem::create_directories(outPath); for (uint32_t fileIdx = 0; fileIdx < vivFile.nFiles; ++fileIdx) { VivEntry &curFile{vivFile.files.at(fileIdx)}; @@ -32,6 +33,11 @@ namespace LibOpenNFS::Shared { std::stringstream out_file_path; out_file_path << outPath << "/" << curFile.filename; + if (std::filesystem::exists(out_file_path.str())) { + LogInfo("Not extracting file %s, because it already exists", out_file_path.str().c_str()); + continue; + } + std::ofstream out(out_file_path.str(), std::ios::out | std::ios::binary); if (!out.is_open()) { LogWarning("Error while creating output file %s", out_file_path.str().c_str()); @@ -43,6 +49,36 @@ namespace LibOpenNFS::Shared { return true; } + bool VivArchive::ExtractFile(std::string const &outPath, VivArchive &vivFile, std::string const &fileName) { + if (!std::filesystem::exists(outPath)) + std::filesystem::create_directories(outPath); + + std::stringstream out_file_path; + out_file_path << outPath << "/" << fileName; + if (std::filesystem::exists(out_file_path.str())) { + return true; + } + + bool fileExtracted = false; + for (uint32_t fileIdx = 0; fileIdx < vivFile.nFiles; ++fileIdx) { + VivEntry &curFile{vivFile.files.at(fileIdx)}; + + if (curFile.filename != fileName) { + continue; + } + + std::ofstream out(out_file_path.str(), std::ios::out | std::ios::binary); + if (!out.is_open()) { + LogWarning("Error while creating output file %s", out_file_path.str().c_str()); + return false; + } + out.write((char *)curFile.data.data(), curFile.data.size()); + out.close(); + } + + return fileExtracted; + } + bool VivArchive::_SerializeIn(std::ifstream &ifstream) { onfs_check(safe_read(ifstream, vivHeader)); diff --git a/Shared/VIV/VivArchive.h b/Shared/VIV/VivArchive.h index d66e45a..e4341da 100644 --- a/Shared/VIV/VivArchive.h +++ b/Shared/VIV/VivArchive.h @@ -14,6 +14,7 @@ namespace LibOpenNFS::Shared { static bool Load(std::string const &vivPath, VivArchive &vivFile); static void Save(std::string const &vivPath, VivArchive &vivFile); static bool Extract(std::string const &outPath, VivArchive &vivFile); + static bool ExtractFile(std::string const &outPath, VivArchive &vivFile, std::string const &fileName); char vivHeader[4]; uint32_t vivSize; From ccaef9ea496927cf707ede9c9f8bf67107af698b Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Sun, 25 Jan 2026 16:06:13 +0100 Subject: [PATCH 2/7] Actually return true when the fedata file was extracted --- Shared/VIV/VivArchive.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Shared/VIV/VivArchive.cpp b/Shared/VIV/VivArchive.cpp index d97766f..1734ffb 100644 --- a/Shared/VIV/VivArchive.cpp +++ b/Shared/VIV/VivArchive.cpp @@ -74,6 +74,8 @@ namespace LibOpenNFS::Shared { } out.write((char *)curFile.data.data(), curFile.data.size()); out.close(); + fileExtracted = true; + break; } return fileExtracted; From 55a57ec5a29982a599150868da33f3e829842fb0 Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Sun, 25 Jan 2026 16:19:03 +0100 Subject: [PATCH 3/7] Clean up Viv::ExtractFile output --- Shared/VIV/VivArchive.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Shared/VIV/VivArchive.cpp b/Shared/VIV/VivArchive.cpp index 1734ffb..4a429df 100644 --- a/Shared/VIV/VivArchive.cpp +++ b/Shared/VIV/VivArchive.cpp @@ -59,7 +59,6 @@ namespace LibOpenNFS::Shared { return true; } - bool fileExtracted = false; for (uint32_t fileIdx = 0; fileIdx < vivFile.nFiles; ++fileIdx) { VivEntry &curFile{vivFile.files.at(fileIdx)}; @@ -74,11 +73,10 @@ namespace LibOpenNFS::Shared { } out.write((char *)curFile.data.data(), curFile.data.size()); out.close(); - fileExtracted = true; - break; + return true; } - return fileExtracted; + return false; } bool VivArchive::_SerializeIn(std::ifstream &ifstream) { From 4713a7c9ced01aa6d002e31cd9f0682bac3fb2ec Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Wed, 28 Jan 2026 17:08:06 +0100 Subject: [PATCH 4/7] Add LoadCarMenuData function for NFS4 --- NFS4/PC/NFS4Loader.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- NFS4/PC/NFS4Loader.h | 2 ++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/NFS4/PC/NFS4Loader.cpp b/NFS4/PC/NFS4Loader.cpp index e6da28f..f7fb951 100644 --- a/NFS4/PC/NFS4Loader.cpp +++ b/NFS4/PC/NFS4Loader.cpp @@ -40,7 +40,7 @@ namespace LibOpenNFS::NFS4 { Car::PhysicsData carPhysicsData; - if (std::filesystem::exists(carOutPath)) { + if (std::filesystem::exists(fcePath) && std::filesystem::exists(fedataPath) && std::filesystem::exists(carpPath)) { LogInfo("VIV has already been extracted to %s, skipping", carOutPath.c_str()); } else { ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); @@ -105,6 +105,45 @@ namespace LibOpenNFS::NFS4 { return track; } + FedataFile Loader::LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath) { + LogInfo("Loading NFS4 car from %s into %s", carBasePath.c_str(), carOutPath.c_str()); + + std::filesystem::path p(carBasePath); + std::string carName = p.filename().replace_extension("").string(); + std::string const fedataFileName = "fedata.eng"; + + std::stringstream vivPath, fcePath, fedataPath; + vivPath << carBasePath; + fcePath << carOutPath; + fedataPath << carOutPath << "/" << fedataFileName; + + if (version == NFSVersion::NFS_4) { + vivPath << "/car.viv"; + fcePath << "/car.fce"; + } else { + // MCO + vivPath << ".viv"; + fcePath << "/part.fce"; + } + + Shared::VivArchive vivFile; + FedataFile fedataFile; + + if (std::filesystem::exists(fedataPath.str()) && std::filesystem::exists(fcePath.str())) { + LogInfo("Fedata file has already been extracted to %s, skipping", carOutPath.c_str()); + } else { + ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); + ASSERT(Shared::VivArchive::ExtractFile(carOutPath, vivFile, fedataFileName), + "Could not extract fedata file from VIV file: " << vivPath.str() << "to: " << carOutPath); + } + ASSERT(NFS4::FceFile::Load(fcePath.str(), fceFile), "Could not load FCE file: " << fcePath.str()); + if (!FedataFile::Load(fedataPath.str(), fedataFile, fceFile.nColours)) { + LogWarning("Could not load FeData file: %s", fedataPath.str().c_str()); + } + + return fedataFile; + } + Car::MetaData Loader::_ParseAssetData(FceFile const &fceFile, FedataFile const &fedataFile, NFSVersion version) { LogInfo("Parsing FCE File into ONFS Structures"); Car::MetaData carMetadata; diff --git a/NFS4/PC/NFS4Loader.h b/NFS4/PC/NFS4Loader.h index dc8e2f7..570a22a 100644 --- a/NFS4/PC/NFS4Loader.h +++ b/NFS4/PC/NFS4Loader.h @@ -17,6 +17,8 @@ namespace LibOpenNFS::NFS4 { static Car LoadCar(std::string const &carBasePath, std::string const &carOutPath, NFSVersion version); static Track LoadTrack(std::string const &trackBasePath); + static FedataFile LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath); + private: static Car::MetaData _ParseAssetData(FceFile const &fceFile, FedataFile const &fedataFile, NFSVersion version); static Car::PhysicsData _ParsePhysicsData(Shared::CarpFile const &carpFile); From 29908bba3b565a53f66b1d3073e03e2da8e44c03 Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Wed, 28 Jan 2026 17:15:34 +0100 Subject: [PATCH 5/7] Fix build issue --- NFS4/PC/NFS4Loader.cpp | 7 ++++--- NFS4/PC/NFS4Loader.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NFS4/PC/NFS4Loader.cpp b/NFS4/PC/NFS4Loader.cpp index f7fb951..f1ed9cb 100644 --- a/NFS4/PC/NFS4Loader.cpp +++ b/NFS4/PC/NFS4Loader.cpp @@ -40,7 +40,7 @@ namespace LibOpenNFS::NFS4 { Car::PhysicsData carPhysicsData; - if (std::filesystem::exists(fcePath) && std::filesystem::exists(fedataPath) && std::filesystem::exists(carpPath)) { + if (std::filesystem::exists(fcePath.str()) && std::filesystem::exists(fedataPath.str()) && std::filesystem::exists(carpPath.str())) { LogInfo("VIV has already been extracted to %s, skipping", carOutPath.c_str()); } else { ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); @@ -105,7 +105,7 @@ namespace LibOpenNFS::NFS4 { return track; } - FedataFile Loader::LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath) { + FedataFile Loader::LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath, NFSVersion version) { LogInfo("Loading NFS4 car from %s into %s", carBasePath.c_str(), carOutPath.c_str()); std::filesystem::path p(carBasePath); @@ -127,9 +127,10 @@ namespace LibOpenNFS::NFS4 { } Shared::VivArchive vivFile; + FceFile fceFile; FedataFile fedataFile; - if (std::filesystem::exists(fedataPath.str()) && std::filesystem::exists(fcePath.str())) { + if (std::filesystem::exists(fedataPath.str())) { LogInfo("Fedata file has already been extracted to %s, skipping", carOutPath.c_str()); } else { ASSERT(Shared::VivArchive::Load(vivPath.str(), vivFile), "Could not open VIV file: " << vivPath.str()); diff --git a/NFS4/PC/NFS4Loader.h b/NFS4/PC/NFS4Loader.h index 570a22a..f5b13f4 100644 --- a/NFS4/PC/NFS4Loader.h +++ b/NFS4/PC/NFS4Loader.h @@ -17,7 +17,7 @@ namespace LibOpenNFS::NFS4 { static Car LoadCar(std::string const &carBasePath, std::string const &carOutPath, NFSVersion version); static Track LoadTrack(std::string const &trackBasePath); - static FedataFile LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath); + static FedataFile LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath, NFSVersion version); private: static Car::MetaData _ParseAssetData(FceFile const &fceFile, FedataFile const &fedataFile, NFSVersion version); From cc211913f480259020d1b5d16c69289a68621ec9 Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Wed, 28 Jan 2026 17:17:09 +0100 Subject: [PATCH 6/7] Fix log entry when loading menu data from NFS4 --- NFS4/PC/NFS4Loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NFS4/PC/NFS4Loader.cpp b/NFS4/PC/NFS4Loader.cpp index f1ed9cb..0164533 100644 --- a/NFS4/PC/NFS4Loader.cpp +++ b/NFS4/PC/NFS4Loader.cpp @@ -106,7 +106,7 @@ namespace LibOpenNFS::NFS4 { } FedataFile Loader::LoadCarMenuData(std::string const &carBasePath, std::string const &carOutPath, NFSVersion version) { - LogInfo("Loading NFS4 car from %s into %s", carBasePath.c_str(), carOutPath.c_str()); + LogInfo("Loading NFS4 car menu data from %s into %s", carBasePath.c_str(), carOutPath.c_str()); std::filesystem::path p(carBasePath); std::string carName = p.filename().replace_extension("").string(); From ffaaecc68a6ca9b18c33dfb91f33a606983bb925 Mon Sep 17 00:00:00 2001 From: Wouter Wijsman Date: Thu, 29 Jan 2026 09:08:19 +0100 Subject: [PATCH 7/7] Do not require loading fcedata before loading fedata for NFS4 --- NFS4/PC/FEDATA/FedataFile.cpp | 19 +++++++++---------- NFS4/PC/FEDATA/FedataFile.h | 2 +- NFS4/PC/NFS4Loader.cpp | 10 +++------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/NFS4/PC/FEDATA/FedataFile.cpp b/NFS4/PC/FEDATA/FedataFile.cpp index 077651f..3a64b40 100644 --- a/NFS4/PC/FEDATA/FedataFile.cpp +++ b/NFS4/PC/FEDATA/FedataFile.cpp @@ -1,10 +1,12 @@ #include "FedataFile.h" + +#include + namespace LibOpenNFS::NFS4 { - bool FedataFile::Load(std::string const &fedataPath, FedataFile &fedataFile, uint8_t const nPriColours) { + bool FedataFile::Load(std::string const &fedataPath, FedataFile &fedataFile) { std::ifstream fedata(fedataPath, std::ios::in | std::ios::binary); - fedataFile.m_nPriColours = nPriColours; bool const loadStatus{fedataFile._SerializeIn(fedata)}; fedata.close(); @@ -31,18 +33,15 @@ namespace LibOpenNFS::NFS4 { // Jump to location of FILEPOS table for car colour names ifstream.seekg(COLOUR_TABLE_OFFSET, std::ios::beg); // Read that table in - std::vector colourNameOffsets(m_nPriColours); - onfs_check(safe_read(ifstream, colourNameOffsets)); + uint32_t colourNameOffset; + onfs_check(safe_read(ifstream, colourNameOffset)); - for (uint8_t colourIdx = 0; colourIdx < m_nPriColours; ++colourIdx) { - ifstream.seekg(colourNameOffsets[colourIdx], std::ios::beg); - std::string colourName; - std::getline(ifstream, colourName, '\0'); + std::string colourName; + ifstream.seekg(colourNameOffset, std::ios::beg); + while (std::getline(ifstream, colourName, '\0')) { primaryColourNames.emplace_back(colourName.begin(), colourName.end()); } - //uint32_t colourNameLength = colourIdx < (fceHeader->nColours - 1) ? (colourNameOffsets[colourIdx + 1] - colourNameOffsets[colourIdx]) : 32; - return true; } diff --git a/NFS4/PC/FEDATA/FedataFile.h b/NFS4/PC/FEDATA/FedataFile.h index 84d34f6..37a620e 100644 --- a/NFS4/PC/FEDATA/FedataFile.h +++ b/NFS4/PC/FEDATA/FedataFile.h @@ -10,7 +10,7 @@ namespace LibOpenNFS::NFS4 { public: FedataFile() = default; - static bool Load(std::string const &fedataPath, FedataFile &fedataFile, uint8_t nPriColours); + static bool Load(std::string const &fedataPath, FedataFile &fedataFile); static void Save(std::string const &fedataPath, FedataFile &fedataFile); std::string menuName; diff --git a/NFS4/PC/NFS4Loader.cpp b/NFS4/PC/NFS4Loader.cpp index 0164533..c11c1b6 100644 --- a/NFS4/PC/NFS4Loader.cpp +++ b/NFS4/PC/NFS4Loader.cpp @@ -48,7 +48,7 @@ namespace LibOpenNFS::NFS4 { "Could not extract VIV file: " << vivPath.str() << "to: " << carOutPath); } ASSERT(NFS4::FceFile::Load(fcePath.str(), fceFile), "Could not load FCE file: " << fcePath.str()); - if (!FedataFile::Load(fedataPath.str(), fedataFile, fceFile.nColours)) { + if (!FedataFile::Load(fedataPath.str(), fedataFile)) { LogWarning("Could not load FeData file: %s", fedataPath.str().c_str()); } if (Shared::CarpFile::Load(carpPath.str(), carpFile)) { @@ -112,18 +112,15 @@ namespace LibOpenNFS::NFS4 { std::string carName = p.filename().replace_extension("").string(); std::string const fedataFileName = "fedata.eng"; - std::stringstream vivPath, fcePath, fedataPath; + std::stringstream vivPath, fedataPath; vivPath << carBasePath; - fcePath << carOutPath; fedataPath << carOutPath << "/" << fedataFileName; if (version == NFSVersion::NFS_4) { vivPath << "/car.viv"; - fcePath << "/car.fce"; } else { // MCO vivPath << ".viv"; - fcePath << "/part.fce"; } Shared::VivArchive vivFile; @@ -137,8 +134,7 @@ namespace LibOpenNFS::NFS4 { ASSERT(Shared::VivArchive::ExtractFile(carOutPath, vivFile, fedataFileName), "Could not extract fedata file from VIV file: " << vivPath.str() << "to: " << carOutPath); } - ASSERT(NFS4::FceFile::Load(fcePath.str(), fceFile), "Could not load FCE file: " << fcePath.str()); - if (!FedataFile::Load(fedataPath.str(), fedataFile, fceFile.nColours)) { + if (!FedataFile::Load(fedataPath.str(), fedataFile)) { LogWarning("Could not load FeData file: %s", fedataPath.str().c_str()); }