Initial prototype - reads the TOC from the ELF and maps the BOLT file
This commit is contained in:
commit
d7a48f39b4
|
@ -0,0 +1,46 @@
|
||||||
|
# .clang-format for native code portion
|
||||||
|
|
||||||
|
BasedOnStyle: Google
|
||||||
|
|
||||||
|
# force T* or T&
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
|
||||||
|
TabWidth: 4
|
||||||
|
IndentWidth: 4
|
||||||
|
UseTab: Always
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakStringLiterals: false
|
||||||
|
|
||||||
|
ColumnLimit: 150
|
||||||
|
CompactNamespaces: false
|
||||||
|
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ContinuationIndentWidth: 0
|
||||||
|
|
||||||
|
# turning this on causes major issues with initializer lists
|
||||||
|
Cpp11BracedListStyle: false
|
||||||
|
SpaceBeforeCpp11BracedList: true
|
||||||
|
|
||||||
|
FixNamespaceComments: true
|
||||||
|
|
||||||
|
NamespaceIndentation: All
|
||||||
|
ReflowComments: true
|
||||||
|
|
||||||
|
SortIncludes: CaseInsensitive
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
SpaceBeforeParens: Never
|
||||||
|
SpacesBeforeTrailingComments: 1
|
|
@ -0,0 +1,5 @@
|
||||||
|
build/
|
||||||
|
/out
|
||||||
|
.cache/
|
||||||
|
/compile_commands.json
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
project(lightningbolt
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
include(Policies)
|
||||||
|
include(ProjectFuncs)
|
||||||
|
|
||||||
|
# default linker
|
||||||
|
if(NOT LIGHTNINGBOLT_LINKER AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||||
|
set(LIGHTNINGBOLT_LINKER "lld")
|
||||||
|
elseif(NOT LIGHTNINGBOLT_LINKER)
|
||||||
|
set(LIGHTNINGBOLT_LINKER "bfd")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
lb_set_alternate_linker()
|
||||||
|
|
||||||
|
add_subdirectory(lib/base)
|
||||||
|
|
||||||
|
# third party vendor dependencies
|
||||||
|
#add_subdirectory(third_party)
|
||||||
|
|
||||||
|
add_executable(lightningbolt
|
||||||
|
src/main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(lightningbolt PRIVATE
|
||||||
|
lb::base
|
||||||
|
)
|
||||||
|
|
||||||
|
lb_target(lightningbolt)
|
|
@ -0,0 +1,22 @@
|
||||||
|
# CMake policy configuration
|
||||||
|
|
||||||
|
# Macro to enable new CMake policy.
|
||||||
|
# Makes this file a *LOT* shorter.
|
||||||
|
macro (_new_cmake_policy policy)
|
||||||
|
if(POLICY ${policy})
|
||||||
|
#message(STATUS "Enabling new policy ${policy}")
|
||||||
|
cmake_policy(SET ${policy} NEW)
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
_new_cmake_policy(CMP0026) # CMake 3.0: Disallow use of the LOCATION property for build targets.
|
||||||
|
_new_cmake_policy(CMP0042) # CMake 3.0+ (2.8.12): MacOS "@rpath" in target's install name
|
||||||
|
_new_cmake_policy(CMP0046) # warn about non-existent dependencies
|
||||||
|
_new_cmake_policy(CMP0048) # CMake 3.0+: project() command now maintains VERSION
|
||||||
|
_new_cmake_policy(CMP0054) # CMake 3.1: Only interpret if() arguments as variables or keywords when unquoted.
|
||||||
|
_new_cmake_policy(CMP0056) # try_compile() linker flags
|
||||||
|
_new_cmake_policy(CMP0066) # CMake 3.7: try_compile(): use per-config flags, like CMAKE_CXX_FLAGS_RELEASE
|
||||||
|
_new_cmake_policy(CMP0067) # CMake 3.8: try_compile(): honor language standard variables (like C++11)
|
||||||
|
_new_cmake_policy(CMP0068) # CMake 3.9+: `RPATH` settings on macOS do not affect `install_name`.
|
||||||
|
_new_cmake_policy(CMP0075) # CMake 3.12+: Include file check macros honor `CMAKE_REQUIRED_LIBRARIES`
|
||||||
|
_new_cmake_policy(CMP0077) # CMake 3.13+: option() honors normal variables.
|
|
@ -0,0 +1,59 @@
|
||||||
|
function(lb_target target)
|
||||||
|
target_compile_definitions(${target} PRIVATE "$<$<CONFIG:DEBUG>:LIGHTNINGBOLT_DEBUG>")
|
||||||
|
#target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR})
|
||||||
|
|
||||||
|
target_compile_features(${target} PRIVATE cxx_std_20)
|
||||||
|
target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/lib ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
set(_LIGHTNINGBOLT_CORE_COMPILE_ARGS -Wall -Wextra)
|
||||||
|
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||||
|
set(_LIGHTNINGBOLT_CORE_COMPILE_ARGS ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS})
|
||||||
|
|
||||||
|
# If on Release use link-time optimizations.
|
||||||
|
# On clang we use ThinLTO for even better build performance.
|
||||||
|
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||||
|
set(_LIGHTNINGBOLT_CORE_COMPILE_ARGS ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS} -flto=thin)
|
||||||
|
target_link_options(${target} PRIVATE -fuse-ld=${LIGHTNINGBOLT_LINKER} -flto=thin)
|
||||||
|
else()
|
||||||
|
set(_LIGHTNINGBOLT_CORE_COMPILE_ARGS ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS} -flto)
|
||||||
|
target_link_options(${target} PRIVATE -fuse-ld=${LIGHTNINGBOLT_LINKER} -flto)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_options(${target} PRIVATE ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
if("asan" IN_LIST LIGHTNINGBOLT_BUILD_FEATURES)
|
||||||
|
# Error if someone's trying to mix asan and tsan together,
|
||||||
|
# they aren't compatible.
|
||||||
|
if("tsan" IN_LIST LIGHTNINGBOLT_BUILD_FEATURES)
|
||||||
|
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Enabling ASAN for target ${target} because it was in LIGHTNINGBOLT_BUILD_FEATURES")
|
||||||
|
target_compile_options(${target} PRIVATE ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS} -fsanitize=address)
|
||||||
|
target_link_libraries(${target} PRIVATE -fsanitize=address)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("tsan" IN_LIST LIGHTNINGBOLT_BUILD_FEATURES)
|
||||||
|
message(STATUS "Enabling TSAN for target ${target} because it was in LIGHTNINGBOLT_BUILD_FEATURES")
|
||||||
|
target_compile_options(${target} PRIVATE ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS} -fsanitize=thread)
|
||||||
|
target_link_libraries(${target} PRIVATE -fsanitize=thread)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("ubsan" IN_LIST LIGHTNINGBOLT_BUILD_FEATURES)
|
||||||
|
message(STATUS "Enabling UBSAN for target ${target} because it was in LIGHTNINGBOLT_BUILD_FEATURES")
|
||||||
|
target_compile_options(${target} PRIVATE ${_LIGHTNINGBOLT_CORE_COMPILE_ARGS} -fsanitize=undefined)
|
||||||
|
target_link_libraries(${target} PRIVATE -fsanitize=undefined)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(lb_set_alternate_linker)
|
||||||
|
find_program(LINKER_EXECUTABLE ld.${LIGHTNINGBOLT_LINKER} ${COLLABVM_LINKER})
|
||||||
|
if(LINKER_EXECUTABLE)
|
||||||
|
message(STATUS "Using ${LIGHTNINGBOLT_LINKER} as linker")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Linker ${LIGHTNINGBOLT_LINKER} does not exist on your system. Please specify one which does or omit this option from your configure command.")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
|
@ -0,0 +1,6 @@
|
||||||
|
add_library(lb_base
|
||||||
|
MmapFile.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
lb_target(lb_base)
|
||||||
|
add_library(lb::base ALIAS lb_base)
|
|
@ -0,0 +1,90 @@
|
||||||
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
|
#include <system_error>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct ErrorOr {
|
||||||
|
private:
|
||||||
|
using Variant = std::variant<T, std::error_code>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr static auto IsVoid = false;
|
||||||
|
ErrorOr() = default;
|
||||||
|
ErrorOr(const ErrorOr&) = default;
|
||||||
|
ErrorOr(ErrorOr&&) = default;
|
||||||
|
|
||||||
|
ErrorOr(const T& v) : maybeVariant(v) {}
|
||||||
|
ErrorOr(const std::error_code& v) : maybeVariant(v) {}
|
||||||
|
|
||||||
|
ErrorOr& operator=(const T& value) {
|
||||||
|
maybeVariant = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr& operator=(const std::error_code& ec) {
|
||||||
|
maybeVariant = ec;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValue() const {
|
||||||
|
if(maybeVariant)
|
||||||
|
return std::holds_alternative<T>(*maybeVariant);
|
||||||
|
|
||||||
|
// No value.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasError() const {
|
||||||
|
if(maybeVariant)
|
||||||
|
return std::holds_alternative<std::error_code>(*maybeVariant);
|
||||||
|
|
||||||
|
// No error. Possibly in the default constructed state
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSERT TODO
|
||||||
|
|
||||||
|
T& Value() { return std::get<T>(maybeVariant.value()); }
|
||||||
|
|
||||||
|
const T& Value() const { return std::get<T>(maybeVariant.value()); }
|
||||||
|
|
||||||
|
operator T&() { return Value(); }
|
||||||
|
operator const T&() { return Value(); }
|
||||||
|
|
||||||
|
std::error_code& Error() { return std::get<std::error_code>(maybeVariant.value()); }
|
||||||
|
|
||||||
|
const std::error_code& Error() const { return std::get<std::error_code>(maybeVariant.value()); }
|
||||||
|
|
||||||
|
operator std::error_code&() { return Error(); }
|
||||||
|
operator const std::error_code&() const { return Error(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<Variant> maybeVariant;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ErrorOr<void> {
|
||||||
|
constexpr static auto IsVoid = true;
|
||||||
|
ErrorOr() = default;
|
||||||
|
ErrorOr(const ErrorOr&) = default;
|
||||||
|
ErrorOr(ErrorOr&&) = default;
|
||||||
|
ErrorOr(const std::error_code& v) : maybeEc(v) {}
|
||||||
|
|
||||||
|
ErrorOr& operator=(const std::error_code& ec) {
|
||||||
|
maybeEc = ec;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasError() const { return maybeEc.has_value(); }
|
||||||
|
|
||||||
|
std::error_code& Error() { return maybeEc.value(); }
|
||||||
|
|
||||||
|
const std::error_code& Error() const { return maybeEc.value(); }
|
||||||
|
|
||||||
|
operator std::error_code&() { return Error(); }
|
||||||
|
operator const std::error_code&() const { return Error(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<std::error_code> maybeEc;
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <base/MmapFile.hpp>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include "MmapFile.linux.cpp"
|
||||||
|
#else
|
||||||
|
#error Invalid platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
|
||||||
|
MmapFile::MmapFile() : impl(std::make_unique<Impl>()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MmapFile::~MmapFile() = default;
|
||||||
|
|
||||||
|
ErrorOr<void> MmapFile::Open(const fs::path& path) {
|
||||||
|
return impl->Open(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MmapFile::Close() {
|
||||||
|
return impl->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* MmapFile::GetMapping() const {
|
||||||
|
return impl->GetMapping();
|
||||||
|
}
|
||||||
|
usize MmapFile::GetMappingSize() const {
|
||||||
|
return impl->GetMappingSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lightningbolt
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
#include <base/Types.hpp>
|
||||||
|
#include <base/ErrorOr.hpp>
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
|
||||||
|
/// A read-only file opened via memory mapping.
|
||||||
|
/// On POSIX systems, we use mmap(2). Etc etc.
|
||||||
|
struct MmapFile {
|
||||||
|
MmapFile();
|
||||||
|
~MmapFile();
|
||||||
|
|
||||||
|
// Opens for read-only mode.
|
||||||
|
ErrorOr<void> Open(const fs::path& path);
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
u8* GetMapping() const;
|
||||||
|
usize GetMappingSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
Unique<Impl> impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "MmapFile.hpp"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
|
||||||
|
struct MmapFile::Impl {
|
||||||
|
|
||||||
|
~Impl() {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
if(mapping) {
|
||||||
|
munmap(mapping, mappingSize);
|
||||||
|
mapping = nullptr;
|
||||||
|
mappingSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<void> Open(const fs::path& path) {
|
||||||
|
int fd = open(path.string().c_str(), O_RDONLY);
|
||||||
|
|
||||||
|
// Error opening file.
|
||||||
|
if(fd == -1)
|
||||||
|
return std::error_code{errno, std::system_category()};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto last = lseek64(fd, 0, SEEK_END);
|
||||||
|
mappingSize = lseek64(fd, 0, SEEK_CUR);
|
||||||
|
lseek64(fd, last, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping = static_cast<u8*>(mmap(nullptr, mappingSize, PROT_READ, MAP_PRIVATE, fd, 0));
|
||||||
|
if(mapping == static_cast<u8*>(MAP_FAILED)) {
|
||||||
|
mappingSize = 0;
|
||||||
|
return std::error_code{errno, std::system_category()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the mapping has successfully been created
|
||||||
|
// we can close the file descriptor instead of needing
|
||||||
|
// to remember it (the kernel will do so for us.)
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
// No error.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* GetMapping() const {
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize GetMappingSize() const {
|
||||||
|
return mappingSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8* mapping;
|
||||||
|
usize mappingSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lightningbolt
|
|
@ -0,0 +1,85 @@
|
||||||
|
#pragma once
|
||||||
|
#include <bit>
|
||||||
|
#include <base/Types.hpp>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <class NativeT, class OffsetType>
|
||||||
|
constexpr NativeT* CreatePointerFromAddend(void* BasePointer, OffsetType addend) noexcept {
|
||||||
|
return std::bit_cast<NativeT*>(static_cast<char*>(BasePointer) + addend);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/// An "auto-resolving" semi-sweet (/s) pointer type.
|
||||||
|
/// This is designed to allow resolving offsets in data for
|
||||||
|
/// games written before 64-bit pointers were common/used at all.
|
||||||
|
/// This allows looking up data a lot easier :)
|
||||||
|
///
|
||||||
|
/// [NativeT] is the type of data this would point to
|
||||||
|
/// [OffsetType] is the type of data the "pointer" is repressented as
|
||||||
|
template <class NativeT, class OffsetType = u32>
|
||||||
|
struct OffsetPtr final {
|
||||||
|
using Type = std::remove_cvref_t<NativeT>;
|
||||||
|
using Pointer = Type*;
|
||||||
|
using ConstPointer = const Type*;
|
||||||
|
|
||||||
|
/// Set the offset. Duh!
|
||||||
|
constexpr void Set(OffsetType newOffset) noexcept { rawOffset = newOffset; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr OffsetType Raw() const noexcept { return rawOffset; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Pointer operator()(void* baseAddr) const noexcept {
|
||||||
|
// While yucky, it should show problem areas which aren't checking things
|
||||||
|
// immediately rather than read invalid data that might do much worse.
|
||||||
|
if(rawOffset == 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return detail::CreatePointerFromAddend<Type>(baseAddr, rawOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class NativeU>
|
||||||
|
constexpr OffsetPtr<NativeU, OffsetType>& PtrCast() {
|
||||||
|
// Safety: The data layout of OffsetPtr<> stays
|
||||||
|
// the exact same regardless of the result type, therefore
|
||||||
|
// even though this is *techinically* UB (? using bit_cast it shouldn't be ?),
|
||||||
|
// this isn't problematic
|
||||||
|
return *std::bit_cast<OffsetPtr<NativeU, OffsetType>*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OffsetType rawOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Like OffsetPtr<T> but for arrays of data
|
||||||
|
template <class NativeT, class OffsetType = u32>
|
||||||
|
struct OffsetArrayPtr final {
|
||||||
|
using Type = std::remove_cvref_t<NativeT>;
|
||||||
|
using Pointer = Type*;
|
||||||
|
using ConstPointer = const Type*;
|
||||||
|
|
||||||
|
using Span = std::span<NativeT>;
|
||||||
|
|
||||||
|
/// Set the offset. Duh!
|
||||||
|
constexpr void Set(OffsetType newOffset) noexcept { rawOffset = newOffset; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr OffsetType Raw() const noexcept { return rawOffset; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Span operator()(void* baseAddr, OffsetType length) const noexcept {
|
||||||
|
// While yucky, it should show problem areas which aren't checking things
|
||||||
|
// immediately rather than read invalid data that might do much worse.
|
||||||
|
if(rawOffset == 0 || length == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return { detail::CreatePointerFromAddend<Type>(baseAddr, rawOffset), length };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OffsetType rawOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ssxtools::core
|
|
@ -0,0 +1,31 @@
|
||||||
|
//! Core types and includes
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
// these are in the global namespace since most libraries
|
||||||
|
// won't try defining anything like this in the global namespace
|
||||||
|
// (and I'd like these types to be used globally a lot more anyways)
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using i8 = std::int8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using i16 = std::int16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
using i64 = std::int64_t;
|
||||||
|
using usize = std::size_t;
|
||||||
|
using isize = std::intptr_t;
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
template <class T, class Deleter = std::default_delete<T>>
|
||||||
|
using Unique = std::unique_ptr<T, Deleter>;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using Ref = std::shared_ptr<T>;
|
||||||
|
|
||||||
|
} // namespace lightningbolt
|
|
@ -0,0 +1,86 @@
|
||||||
|
#include <base/Types.hpp>
|
||||||
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace lightningbolt {
|
||||||
|
|
||||||
|
namespace elf {
|
||||||
|
|
||||||
|
/// Table entry in the ELF. We use this to create file names.
|
||||||
|
struct [[gnu::packed]] BoltTableEntry {
|
||||||
|
/// Pointer to filename. Should be adjusted
|
||||||
|
u32 filenamePtr;
|
||||||
|
u16 entryId; // (GID >> 8) | ID
|
||||||
|
u16 groupId; // (entryId & 0xff00)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Offsets in the ELF to the table.
|
||||||
|
struct BoltTableOffsets {
|
||||||
|
u32 usTable;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Convert a address to ELF file offset.
|
||||||
|
constexpr u32 AddressToElfFileOffset(u32 address) {
|
||||||
|
constexpr u32 LoadAddress = 0x00100000;
|
||||||
|
constexpr u32 SectionOffset = 0x80;
|
||||||
|
return (address - LoadAddress) + SectionOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr BoltTableOffsets BoltTableOffsets = {
|
||||||
|
.usTable = AddressToElfFileOffset(0x0033d400)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace elf
|
||||||
|
|
||||||
|
struct [[gnu::packed]] BoltGroupEntry {
|
||||||
|
u32 unk;
|
||||||
|
u32 fileSize;
|
||||||
|
u32 fileOffset;
|
||||||
|
u32 unk3; // name hash?
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] BoltGroupDescriptor {
|
||||||
|
u8 unk;
|
||||||
|
u8 unk2;
|
||||||
|
u8 unk3;
|
||||||
|
u8 entryCount;
|
||||||
|
|
||||||
|
u32 groupSize;
|
||||||
|
u32 groupOffset;
|
||||||
|
|
||||||
|
u32 EntryCount() {
|
||||||
|
// Special case: 0x0 == 256 entries.
|
||||||
|
// I have NO idea why they did it like this,
|
||||||
|
// this really seems extra when they could have
|
||||||
|
// used a short field in the same exact space.
|
||||||
|
if(entryCount == 0x0)
|
||||||
|
return 256;
|
||||||
|
return entryCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<BoltGroupEntry> Entries(u8* base) { return { std::bit_cast<BoltGroupEntry*>(base + groupOffset), EntryCount() }; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] BoltLibraryHeader {
|
||||||
|
static constexpr char VALID_MAGIC[] = "BOLT\r\n";
|
||||||
|
char magic[6];
|
||||||
|
|
||||||
|
u8 unk;
|
||||||
|
u8 unk2;
|
||||||
|
|
||||||
|
u8 unk3;
|
||||||
|
u8 unk4;
|
||||||
|
u8 unk5;
|
||||||
|
u8 groupCount;
|
||||||
|
|
||||||
|
u32 libSize;
|
||||||
|
|
||||||
|
std::span<BoltGroupDescriptor> GroupDescriptors() {
|
||||||
|
// The group descriptors are after the primary library header
|
||||||
|
return { std::bit_cast<BoltGroupDescriptor*>(this + 1), groupCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Validate() const { return !std::memcmp(&magic[0], &VALID_MAGIC[0], sizeof(magic)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lightningbolt
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include <base/MmapFile.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <structs/BoltStructs.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
struct ParsedTableEntry {
|
||||||
|
std::string_view filename;
|
||||||
|
u16 index;
|
||||||
|
u32 gid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoltReader {
|
||||||
|
BoltReader() {}
|
||||||
|
|
||||||
|
ErrorOr<void> OpenBolt(const lightningbolt::fs::path& path) {
|
||||||
|
auto p = path;
|
||||||
|
p.replace_filename("SLUS_201.14");
|
||||||
|
|
||||||
|
if(auto error = elfFile.Open(p); error.HasError())
|
||||||
|
return error;
|
||||||
|
|
||||||
|
// Load table entries.
|
||||||
|
GetTableEntries();
|
||||||
|
|
||||||
|
// Load the BOLT file
|
||||||
|
if(auto error = boltFile.Open(path); error.HasError())
|
||||||
|
return error;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ParsedTableEntry>& GetTableEntries() {
|
||||||
|
if(entryTable.empty()) {
|
||||||
|
auto* base = elfFile.GetMapping();
|
||||||
|
auto* table = std::bit_cast<lightningbolt::elf::BoltTableEntry*>(base + lightningbolt::elf::BoltTableOffsets.usTable);
|
||||||
|
while(table->filenamePtr != 0x0) {
|
||||||
|
auto string_offset = lightningbolt::elf::AddressToElfFileOffset(table->filenamePtr);
|
||||||
|
|
||||||
|
ParsedTableEntry te;
|
||||||
|
te.filename = { std::bit_cast<char*>(base + string_offset) };
|
||||||
|
te.index = table->entryId;
|
||||||
|
te.gid = table->groupId;
|
||||||
|
|
||||||
|
if(te.filename == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::cout << std::format("te: {} {:04x} {:04x}\n", te.filename, te.index, te.gid);
|
||||||
|
entryTable.emplace_back(te);
|
||||||
|
|
||||||
|
table++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ELF file isn't needed after this so unmap it
|
||||||
|
elfFile.Close();
|
||||||
|
}
|
||||||
|
return entryTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
void ForEachFile(F f) {
|
||||||
|
//for()()
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ParsedTableEntry> entryTable;
|
||||||
|
lightningbolt::MmapFile elfFile;
|
||||||
|
lightningbolt::MmapFile boltFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BoltReader reader;
|
||||||
|
if(auto error = reader.OpenBolt(lightningbolt::fs::current_path() / "ASSETS.BLT"); error.HasError()) {
|
||||||
|
std::cout << "Error opening Bolt file: " << error.Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue