Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,13 +553,19 @@ int cli::run(size_t argc, const char** argv)
{
std::filesystem::path path(*rit);
path = path.lexically_normal();
rvutils::pbo::pbofile pbo(path);
rvutils::pbo::pbofile pbo;
pbo.open(path);
if (!pbo.good())
{
std::cout << "Failed to parse PBO '" << path << "'.";
m_good = false;
continue;
}
if (!static_cast<sqf::fileio::impl_default&>(m_runtime.fileio()).add_pbo_mapping(pbo))
{
m_good = false;
continue;
}
static_cast<sqf::fileio::impl_default&>(m_runtime.fileio()).add_pbo_mapping(pbo);
rvutils::pbo::pbofile::reader reader;
if (pbo.read("config.cpp", reader))
{
Expand Down Expand Up @@ -765,6 +771,7 @@ int cli::run(size_t argc, const char** argv)

handle_files();

m_good = m_good && !m_logger.has_fatal_error();
if (!m_good || m_parse_only)
{
if (verbose() && !m_good) { std::cout << "Exiting due to error." << std::endl; }
Expand Down Expand Up @@ -909,6 +916,7 @@ int cli::run(size_t argc, const char** argv)
}
} while (!m_automated && !m_runtime.is_exit_requested());
}
if (m_logger.has_fatal_error()) { return -1; }
auto exitcode = m_runtime.exit_code();
if (exitcode.has_value())
{
Expand Down
15 changes: 13 additions & 2 deletions src/cli/cli.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ namespace TCLAP

class cli
{
StdOutLogger m_logger;
class cli_logger : public StdOutLogger
{
bool m_fatal_error = false;
public:
void log(const LogMessageBase& message) override
{
if (message.getLevel() == loglevel::fatal) { m_fatal_error = true; }
StdOutLogger::log(message);
}
bool has_fatal_error() const { return m_fatal_error; }
};
cli_logger m_logger;
sqf::runtime::runtime m_runtime;
bool m_cli_file;
bool m_parse_only;
Expand All @@ -32,4 +43,4 @@ class cli
int run(size_t argc, const char** argv);

bool verbose() const { return m_logger.isEnabled(loglevel::verbose); }
};
};
154 changes: 54 additions & 100 deletions src/fileio/default.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <sstream>
#include <fstream>
#include <iterator>
#include <stdexcept>

#ifdef DF__SQF_FILEIO__TRACE_REESOLVE
#include <iostream>
Expand All @@ -31,6 +32,18 @@ inline bool file_exists(std::filesystem::path p)
return infile.good();
}

static std::string pbo_virtual_path(std::string path)
{
std::replace(path.begin(), path.end(), '\\', '/');
auto first = path.find_first_not_of('/');
path = first == std::string::npos ? "/" : "/" + path.substr(first);
path = std::filesystem::path(path).lexically_normal().generic_string();
std::transform(path.begin(), path.end(), path.begin(), [](unsigned char c) {
return static_cast<char>(c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c);
});
return path;
}

std::optional<sqf::runtime::fileio::pathinfo> sqf::fileio::impl_default::get_info_virtual(std::string_view viewVirtual, sqf::runtime::fileio::pathinfo current) const
{
// Create & Cleanse stuff
Expand All @@ -48,6 +61,19 @@ std::optional<sqf::runtime::fileio::pathinfo> sqf::fileio::impl_default::get_inf
return {};
}

auto pbo_path = virt;
if (virt.front() != '/' && !current.virtual_.empty())
{
auto parent = current.virtual_;
std::replace(parent.begin(), parent.end(), '\\', '/');
pbo_path = std::filesystem::path(parent).parent_path().generic_string() + "/" + virt;
}
auto pbo_entry = m_pbo_files.find(pbo_virtual_path(pbo_path));
if (pbo_entry != m_pbo_files.end())
{
return pbo_entry->second;
}

// Prepare local tree-node list
std::vector<std::shared_ptr<path_element>> nodes;
nodes.push_back(m_virtual_file_root);
Expand Down Expand Up @@ -202,96 +228,49 @@ std::optional<sqf::runtime::fileio::pathinfo> sqf::fileio::impl_default::get_inf
return {};
}

void sqf::fileio::impl_default::add_pbo_mapping(rvutils::pbo::pbofile& pbo)
bool sqf::fileio::impl_default::add_pbo_mapping(rvutils::pbo::pbofile& pbo)
{
if (m_pbos.find(pbo.path().string()) != m_pbos.end())
const auto archive = pbo.path().lexically_normal().string();
if (m_pbos.find(archive) != m_pbos.end())
{
log(logmessage::fileio::PBOAlreadyAdded(pbo.path().string()));
return;
return true;
}
auto prefix_optional = pbo.attribute("prefix");
if (!prefix_optional.has_value())
{
log(logmessage::fileio::PBOHasNoPrefixAttribute(pbo.path().string()));
return;
return false;
}

m_pbos[pbo.path().lexically_normal().string()] = pbo;
std::filesystem::path prefix(*prefix_optional);


// We need to register all files with the virtual pathing
for (auto& file_desc : pbo.files())
m_pbos[archive] = pbo;
for (const auto& file : pbo.files())
{
// Construct file path
auto file_path = (prefix / file_desc.name).lexically_normal();
auto path_iter = file_path.begin();

// Navigate to last available virtual file node from root node
auto nav = m_virtual_file_root->next.find(path_iter->string());
if (nav == m_virtual_file_root->next.end())
{
// Setup path_element for inserting
auto el = std::make_shared<path_element>();
auto tmp = path_iter; tmp++; // Fancy hack because for some reason std::filesystem::path lacks `path + number`
el->virtual_full = std::accumulate(file_path.begin(), tmp, std::filesystem::path{}, std::divides{}).string();
el->physical.push_back(pbo.path().string());

// Insert next nav node
auto [iter, success] = m_virtual_file_root->next.emplace(std::make_pair(path_iter->string(), el));
nav = iter;

++path_iter;
}
else
const auto virtual_path = pbo_virtual_path(*prefix_optional + "/" + file.name);
auto [entry, inserted] = m_pbo_files.emplace(virtual_path,
sqf::runtime::fileio::pathinfo(archive, file.name, virtual_path));
if (!inserted)
{
++path_iter;
std::unordered_map<std::string, std::shared_ptr<path_element>>::iterator nextnav;
while ((nextnav = nav->second->next.find(path_iter->string())) != nav->second->next.end() && path_iter != file_path.end())
{
nav = nextnav;
path_iter++;
}
}

// Check we did not iterated the whole of file_path
if (path_iter == file_path.end())
{
log(logmessage::fileio::PBOFileAlreadyRegistered(pbo.path().string(), file_path.string()));
continue;
}

// Insert until we hit the file-element
while (path_iter != file_path.end())
{
// Setup path_element for inserting
auto el = std::make_shared<path_element>();
auto tmp = path_iter; tmp++; // Fancy hack because for some reason std::filesystem::path lacks `path + number`
el->virtual_full = std::accumulate(file_path.begin(), tmp, std::filesystem::path{}, std::divides{}).string();
el->physical.push_back(pbo.path().string());

// Insert next nav node
auto [iter, success] = nav->second->next.emplace(std::make_pair(path_iter->string(), el));
nav = iter;

++path_iter;
log(logmessage::fileio::PBOFileAlreadyRegistered(archive, virtual_path));
}
}
return true;
}
void sqf::fileio::impl_default::add_pbo_mapping(std::filesystem::path p)
bool sqf::fileio::impl_default::add_pbo_mapping(std::filesystem::path p)
{
if (m_pbos.find(p.string()) != m_pbos.end())
{
log(logmessage::fileio::PBOAlreadyAdded(p.string()));
return;
return true;
}
rvutils::pbo::pbofile pbo(p);
rvutils::pbo::pbofile pbo;
pbo.open(p);
if (!pbo.good())
{
log(logmessage::fileio::FailedToParsePBO(p.string()));
return;
return false;
}
add_pbo_mapping(pbo);
return add_pbo_mapping(pbo);
}

void sqf::fileio::impl_default::add_mapping(std::string_view viewPhysical, std::string_view viewVirtual)
Expand Down Expand Up @@ -337,45 +316,20 @@ void sqf::fileio::impl_default::add_mapping(std::string_view viewPhysical, std::
std::string sqf::fileio::impl_default::read_file(sqf::runtime::fileio::pathinfo info) const
{
std::filesystem::path physical = info.physical;
if (physical.extension() == ".pbo")
if (!info.additional.empty())
{
auto res = m_pbos.find(physical.lexically_normal().string());
if (res == m_pbos.end())
{
log(logmessage::fileio::PBOFileNotFound(physical.lexically_normal().string()));
return {};
}
else
auto archive = m_pbos.find(physical.lexically_normal().string());
rvutils::pbo::pbofile::reader reader;
if (archive != m_pbos.end() && archive->second.read(info.additional, reader))
{
auto prefix_optional = res->second.attribute("prefix");
if (!prefix_optional.has_value())
{
log(logmessage::fileio::PBOHasNoPrefixAttribute(physical.lexically_normal().string()));
return {};
}
auto prefix = prefix_optional.value();
auto pbo_path = info.virtual_;

if (pbo_path.length() > prefix.length() + 1)
{
pbo_path = pbo_path.substr(prefix.length() + 1);
}
std::transform(pbo_path.begin(), pbo_path.end(), pbo_path.begin(), [](char c) -> char { return c == '/' ? '\\' : c; });

rvutils::pbo::pbofile::reader reader;
if (res->second.read(pbo_path, reader))
{
std::string str;
str.resize(reader.descriptor().size);
reader.read(str.data(), reader.descriptor().size);
return str;
}
else
std::string contents(reader.descriptor().size, '\0');
if (reader.read(contents.data(), contents.size()) == contents.size())
{
log(logmessage::fileio::PBOFailedToReadFile(physical.lexically_normal().string(), pbo_path));
return {};
return contents;
}
}
log(logmessage::fileio::PBOFailedToReadFile(info.physical, info.additional));
throw std::runtime_error("Failed to read PBO member '" + info.additional + "' from '" + info.physical + "'.");
}
else
{
Expand Down
5 changes: 3 additions & 2 deletions src/fileio/default.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace sqf::fileio

std::vector<std::shared_ptr<path_element>> m_path_elements;
std::unordered_map<std::string, rvutils::pbo::pbofile> m_pbos;
std::unordered_map<std::string, sqf::runtime::fileio::pathinfo> m_pbo_files;


void get_directories_recursive(std::vector<std::string>& paths, const std::shared_ptr<path_element>& el) const
Expand Down Expand Up @@ -59,8 +60,8 @@ namespace sqf::fileio
m_virtual_file_root->virtual_full = "/";
m_path_elements.push_back(m_virtual_file_root);
}
void add_pbo_mapping(rvutils::pbo::pbofile& pbo);
void add_pbo_mapping(std::filesystem::path p);
bool add_pbo_mapping(rvutils::pbo::pbofile& pbo);
bool add_pbo_mapping(std::filesystem::path p);
virtual std::optional<sqf::runtime::fileio::pathinfo> get_info(std::string_view view, sqf::runtime::fileio::pathinfo current) const override
{
auto res = get_info_virtual(view, current);
Expand Down
5 changes: 4 additions & 1 deletion src/parser/preprocessor/default.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,10 @@ std::string sqf::parser::preprocessor::impl_default::instance::parse_ppinstructi
}
const auto& physical = include_path_info->physical;
auto res = std::find_if(m_file_scopes.begin(), m_file_scopes.end(),
[physical](file_scope& parent) -> bool { return parent.path.physical == physical; });
[&include_path_info](file_scope& parent) -> bool {
return parent.path.physical == include_path_info->physical &&
parent.path.additional == include_path_info->additional;
});
if (res != m_file_scopes.end())
{
m_errflag = true;
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -2090,7 +2090,7 @@ namespace logmessage {
PBOFailedToReadFile(std::string_view path, std::string_view file) :
FileIoBase(level, errorCode, {}),
m_path(path),
m_file(path)
m_file(file)
{
}
[[nodiscard]] std::string formatMessage() const override;
Expand Down
4 changes: 2 additions & 2 deletions src/rvutils/pbofile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ namespace rvutils::pbo
}
remaining = remaining < bytes ? remaining : bytes;
m_file.read(arr, remaining);
return (size_t)remaining;
return static_cast<size_t>(m_file.gcount());
}
std::streampos tell()
{
Expand Down Expand Up @@ -1465,4 +1465,4 @@ namespace rvutils::pbo
return true;
}
};
}
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ endfunction()
add_subdirectory(sqf)
add_subdirectory(preprocess)
add_subdirectory(cba)
add_subdirectory(fileio)
8 changes: 8 additions & 0 deletions tests/fileio/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
find_package(Python3 COMPONENTS Interpreter QUIET)
if(Python3_Interpreter_FOUND)
add_test(NAME fileio.pbo
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/pbo.py $<TARGET_FILE:sqfvm>)
set_tests_properties(fileio.pbo PROPERTIES TIMEOUT 60)
else()
message(STATUS "Python3 not found: PBO CLI regression tests are unavailable")
endif()
Loading