There's still LOTS of work to do with the ziff stream... I hate IFF, and this weird crusty Z-Axis deriviative is NO better.
This commit is contained in:
Lily Tsuru 2024-02-26 01:50:21 -05:00
commit 2c060e8f4c
28 changed files with 1037 additions and 0 deletions

44
.clang-format Executable file
View File

@ -0,0 +1,44 @@
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: 0
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

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
# specifically for YAML
[{yml, yaml}]
indent_style = space

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
build/
/out
cmake-build-*/
/.idea
.cache/
/compile_commands.json

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Lily Tsuru <lily@crustywindo.ws>

25
CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.15)
project(dmtools
LANGUAGES CXX
)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(Policies)
include(CompilerFlags)
include(ProjectFuncs)
# required system dependencies
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
dmtools_set_alternate_linker()
# libraries
add_subdirectory(lib/base)
add_subdirectory(lib/dmbmx)
# programs
add_subdirectory(progs/zalgo)

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2023 The DMBMX2Tools Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

72
cmake/CompilerFlags.cmake Normal file
View File

@ -0,0 +1,72 @@
# Core compile arguments used for dmtools
set(DMTOOLS_CORE_COMPILE_ARGS "-Wall -Wformat=2 -fstrict-flex-arrays=3 -fstack-clash-protection -fstack-protector-strong ")
set(DMTOOLS_CORE_LINKER_ARGS "-fuse-ld=${DMTOOLS_LINKER}")
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
set(DMTOOLS_CORE_COMPILE_ARGS "${DMTOOLS_CORE_COMPILE_ARGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3")
# 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(DMTOOLS_CORE_COMPILE_ARGS "${DMTOOLS_CORE_COMPILE_ARGS} -flto=thin")
set(DMTOOLS_CORE_LINKER_ARGS "${DMTOOLS_CORE_LINKER_ARGS} -flto=thin")
else()
set(DMTOOLS_CORE_COMPILE_ARGS "${DMTOOLS_CORE_COMPILE_ARGS} -flto")
set(DMTOOLS_CORE_LINKER_ARGS "${DMTOOLS_CORE_LINKER_ARGS} -flto")
endif()
else()
# TODO: This currently assumes libstdc++, later on we should *probably* set this with some detection
# to check the current c++ standard library (but we only will compile and run with libstdc++ anyways since libc++ is a bit lacking with even c++17, so..)
set(DMTOOLS_CORE_COMPILE_ARGS "${DMTOOLS_CORE_COMPILE_ARGS} -D_GLIBCXX_ASSERTIONS")
endif()
set(_DMTOOLS_CORE_WANTED_SANITIZERS "")
if("asan" IN_LIST DMTOOLS_BUILD_FEATURES)
# Error if someone's trying to mix asan and tsan together,
# they aren't compatible.
if("tsan" IN_LIST DMTOOLS_BUILD_FEATURES)
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
endif()
message(STATUS "Enabling ASAN because it was in DMTOOLS_BUILD_FEATURES")
list(APPEND _DMTOOLS_CORE_WANTED_SANITIZERS "address")
endif()
if("tsan" IN_LIST DMTOOLS_BUILD_FEATURES)
if("asan" IN_LIST DMTOOLS_BUILD_FEATURES)
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
endif()
message(STATUS "Enabling TSAN because it was in DMTOOLS_BUILD_FEATURES")
list(APPEND _DMTOOLS_CORE_WANTED_SANITIZERS "thread")
endif()
if("ubsan" IN_LIST DMTOOLS_BUILD_FEATURES)
message(STATUS "Enabling UBSAN because it was in DMTOOLS_BUILD_FEATURES")
list(APPEND _DMTOOLS_CORE_WANTED_SANITIZERS "undefined")
endif()
list(LENGTH _DMTOOLS_CORE_WANTED_SANITIZERS _DMTOOLS_CORE_WANTED_SANITIZERS_LENGTH)
if(NOT _DMTOOLS_CORE_WANTED_SANITIZERS_LENGTH EQUAL 0)
list(JOIN _DMTOOLS_CORE_WANTED_SANITIZERS "," _DMTOOLS_CORE_WANTED_SANITIZERS_ARG)
message(STATUS "Enabled sanitizers: ${_DMTOOLS_CORE_WANTED_SANITIZERS_ARG}")
set(DMTOOLS_CORE_COMPILE_ARGS "${DMTOOLS_CORE_COMPILE_ARGS} -fsanitize=${_COLLABVM_CORE_WANTED_SANITIZERS_ARG}")
set(DMTOOLS_CORE_LINKER_ARGS "${DMTOOLS_CORE_LINKER_ARGS} -fsanitize=${_COLLABVM_CORE_WANTED_SANITIZERS_ARG}")
endif()
# Set core CMake toolchain variables so that they get applied to all projects.
# A bit nasty, but /shrug, this way our third party libraries can be mostly sanitized/etc as well.
set(CMAKE_C_FLAGS "${DMTOOLS_CORE_COMPILE_ARGS}")
set(CMAKE_CXX_FLAGS "${DMTOOLS_CORE_COMPILE_ARGS}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -O0 -g3")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -O3 -g3")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -O3 -g3")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3")
set(CMAKE_EXE_LINKER_FLAGS "${DMTOOLS_CORE_LINKER_ARGS} -Wl,-z,noexecstack,-z,relro,-z,now")

22
cmake/Policies.cmake Normal file
View File

@ -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.

24
cmake/ProjectFuncs.cmake Normal file
View File

@ -0,0 +1,24 @@
function(dmtools_target target)
target_compile_definitions(${target} PRIVATE "$<$<CONFIG:DEBUG>:DMTOOLS_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})
endfunction()
function(dmtools_set_alternate_linker)
find_program(LINKER_EXECUTABLE ld.${DMTOOLS_LINKER} ${COLLABVM_LINKER})
if(LINKER_EXECUTABLE)
message(STATUS "Using ${DMTOOLS_LINKER} as linker")
else()
message(FATAL_ERROR "Linker ${DMTOOLS_LINKER} does not exist on your system. Please specify one which does or omit this option from your configure command.")
endif()
endfunction()
# Set a default linker if the user never provided one.
# This defaults based on the detected compiler to the "best" linker possible
if(NOT DMTOOLS_LINKER AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(DMTOOLS_LINKER "lld")
elseif(NOT DMTOOLS_LINKER)
set(DMTOOLS_LINKER "bfd")
endif()

7
lib/base/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
add_library(dmtoolsBase
io/Stream.cpp
io/FileStream.cpp
io/MemoryStream.cpp
)
dmtools_target(dmtoolsBase)

104
lib/base/ErrorOr.hpp Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <optional>
#include <system_error>
#include <variant>
namespace dmtools {
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;
};
inline std::error_code ThrowErrno() {
return { errno, std::system_category() };
}
} // namespace dmtools

35
lib/base/FixedString.hpp Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <base/Types.hpp>
#include <string_view>
namespace dmtools {
/// A compile-time string. Usable as a C++20 cNTTP.
template <usize N>
struct FixedString {
char buf[N + 1] {};
constexpr FixedString(const char* s) {
for(usize i = 0; i != N; ++i)
buf[i] = s[i];
}
constexpr operator const char*() const {
return buf;
}
constexpr operator std::string_view() const {
return { buf, N };
}
[[nodiscard]] constexpr usize Length() const { return N; }
};
template <usize N>
FixedString(char const (&)[N]) -> FixedString<N - 1>;
} // namespace ssxtools::core

24
lib/base/FourCC.hpp Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <bit>
#include <base/FixedString.hpp>
#include <base/Types.hpp>
namespace dmtools {
/// strong type for FourCCs
enum class FourCCT : u32 {};
/// Compile-time FourCC generation
template <FixedString fccString, std::endian Endian = std::endian::big>
consteval FourCCT FourCC() {
static_assert(fccString.Length() == 4, "Provided string is not a FourCC");
switch(Endian) {
case std::endian::little: return static_cast<FourCCT>((fccString[0] << 24) | (fccString[1] << 16) | (fccString[2] << 8) | fccString[3]);
case std::endian::big: return static_cast<FourCCT>((fccString[0]) | (fccString[1] << 8) | (fccString[2] << 16) | (fccString[3] << 24));
}
}
} // namespace ssxtools::core

88
lib/base/OffsetPtr.hpp Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <bit>
#include <base/Types.hpp>
#include <type_traits>
#include <span>
namespace dmtools {
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

19
lib/base/Types.hpp Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <cstdint>
#include <memory>
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;

View File

@ -0,0 +1,90 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#include <base/io/FileStream.hpp>
#include <cstdio>
namespace dmtools::io {
constexpr auto DmToLibc(StreamSeekDirection dir) {
using enum StreamSeekDirection;
switch(dir) {
case Beg: return SEEK_SET;
case Cur: return SEEK_CUR;
case End: return SEEK_END;
default: return SEEK_SET;
}
}
constexpr auto DmOpenMode(FileStream::OpenMode mode) {
using enum FileStream::OpenMode;
switch(mode) {
case Read: return "rb";
case ReadWrite: return "wb+";
}
}
FileStream::~FileStream() {
Close();
}
bool FileStream::Open(const std::string& filename, OpenMode mode) {
file = std::fopen(filename.c_str(), DmOpenMode(mode));
return Ok();
}
void FileStream::Flush() {
fflush(static_cast<std::FILE*>(file));
}
void FileStream::Close() {
if(Ok()) {
fclose(static_cast<std::FILE*>(file));
}
}
StreamDiff FileStream::ReadSome(u8* buffer, usize length) {
if(Ok())
return std::fread(static_cast<u8*>(buffer), 1, length, static_cast<std::FILE*>(file));
return -1;
}
StreamDiff FileStream::WriteSome(const u8* buffer, usize length) {
if(Ok())
return std::fwrite(static_cast<const void*>(buffer), length, 1, static_cast<std::FILE*>(file));
return -1;
}
StreamDiff FileStream::Seek(StreamDiff where, StreamSeekDirection dir) {
if(!Ok())
return -1;
#ifdef __GLIBC__
return fseeko64(static_cast<std::FILE*>(file), where, DmToLibc(dir));
#else
return fseek(static_cast<std::FILE*>(file), where, DmToLibc(dir));
#endif
}
StreamDiff FileStream::Tell() const {
if(!Ok())
return -1;
#ifdef __GLIBC__
return ftello64(static_cast<std::FILE*>(file));
#else
return ftell(static_cast<std::FILE*>(file));
#endif
}
bool FileStream::Ok() const {
return file != nullptr;
}
bool FileStream::Eof() const {
if(!Ok())
return true;
return feof(static_cast<std::FILE*>(file));
}
} // namespace dmtools::io

View File

@ -0,0 +1,41 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <base/io/Stream.hpp>
namespace dmtools::io {
/// A I/O stream using libc FILE*.
struct FileStream : public Stream {
enum class OpenMode : u8 {
Read,
ReadWrite
};
virtual ~FileStream();
bool Open(const std::string& path, OpenMode mode);
void Flush() override;
void Close();
StreamDiff ReadSome(u8* buffer, usize length) override;
StreamDiff WriteSome(const u8* buffer, usize length) override;
StreamDiff Seek(StreamDiff where, StreamSeekDirection dir) override;
StreamDiff Tell() const override;
bool Ok() const override;
bool Eof() const override;
private:
void* file{};
};
}

View File

@ -0,0 +1,56 @@
#include <base/io/MemoryStream.hpp>
#include <cstring>
namespace dmtools::io {
MemoryStream::MemoryStream(u8* memory, usize size) {
beg = memory;
cur = beg;
end = memory + size;
this->size = size;
}
MemoryStream::~MemoryStream() {
}
StreamDiff MemoryStream::ReadSome(u8* buffer, usize length) {
if(Eof())
return -1;
memcpy(&buffer[0], cur, length);
cur += length;
return length;
}
StreamDiff MemoryStream::WriteSome(const u8* buffer, usize length) {
return -1;
};
StreamDiff MemoryStream::Seek(StreamDiff where, StreamSeekDirection dir) {
using enum StreamSeekDirection;
auto last = Tell();
switch(dir) {
case Beg:
cur = beg + where;
break;
case Cur:
cur += where;
break;
case End: // TODO
cur = end;
break;
}
return last;
}
StreamDiff MemoryStream::Tell() const {
return (cur - beg);
}
bool MemoryStream::Ok() const {
return !Eof();
}
bool MemoryStream::Eof() const {
return cur == end;
};
} // namespace dmtools::io

View File

@ -0,0 +1,30 @@
#include <base/io/Stream.hpp>
namespace dmtools::io {
struct MemoryStream : public Stream {
MemoryStream(u8* memory, usize size);
virtual ~MemoryStream();
StreamDiff ReadSome(u8* buffer, usize length) override;
StreamDiff WriteSome(const u8* buffer, usize length) override;
StreamDiff Seek(StreamDiff where, StreamSeekDirection dir) override;
StreamDiff Tell() const override;
bool Ok() const override;
bool Eof() const override;
private:
u8* beg;
u8* cur;
u8* end;
usize size;
};
}

13
lib/base/io/Stream.cpp Normal file
View File

@ -0,0 +1,13 @@
#include <base/io/Stream.hpp>
namespace dmtools::io {
StreamDiff Stream::ReadSome(std::vector<u8>& buffer) {
return ReadSome(buffer.data(), buffer.size());
}
StreamDiff Stream::WriteSome(const std::vector<u8>& buffer) {
return WriteSome(buffer.data(), buffer.size());
}
} // namespace dmtools::io

74
lib/base/io/Stream.hpp Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#pragma once
#include <base/Types.hpp>
#include <vector>
namespace dmtools::io {
enum class StreamSeekDirection : u8 {
Beg,
Cur,
End
};
using StreamDiff = i64;
// TODO: Communicate error conditions using ErrorOr<> instead of
// -1 values.
/// A abstract stream of bytes. We use a custom implementation
/// instead of C++ IO streams mostly for control reasons, and
/// also to avoid exceptions (we'll use ErrorOr for errors later on)
struct Stream {
Stream() = default;
virtual ~Stream() = default;
Stream(const Stream&) = delete;
Stream(Stream&&) = delete;
virtual void Flush() { return; }
virtual StreamDiff ReadSome(u8* buffer, usize length) = 0;
StreamDiff ReadSome(std::vector<u8>& buffer);
virtual StreamDiff WriteSome(const u8* buffer, usize length) {
// Default implementation disallows writes. Can be changed
return -1;
}
StreamDiff WriteSome(const std::vector<u8>& buffer);
/// Reads a single POD type from stream
template <class T>
inline bool ReadObject(T& obj) {
auto sz = ReadSome(std::bit_cast<u8*>(&obj), sizeof(T));
if(sz != sizeof(T))
return false;
return true;
}
template <class T>
inline bool WriteObject(const T& obj) {
auto sz = WriteSome(std::bit_cast<const u8*>(&obj), sizeof(T));
if(sz != sizeof(T))
return false;
return true;
}
virtual StreamDiff Seek(StreamDiff where, StreamSeekDirection dir) = 0;
virtual StreamDiff Tell() const = 0;
virtual bool Ok() const = 0;
virtual bool Eof() const = 0;
inline operator bool() const {
return Ok() && !Eof();
}
};
} // namespace dmtools

9
lib/dmbmx/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
add_library(dmtoolsDmBmx
ZiffInputStream.cpp
)
dmtools_target(dmtoolsDmBmx)
target_link_libraries(dmtoolsDmBmx
dmtoolsBase
)

42
lib/dmbmx/ZHash.hpp Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#include <base/Types.hpp>
namespace dmtools::dmbmx {
/// Z-Axis' variant of the PJW hash algorithm.
///
/// # Safety
/// This function should *not* be given any input other than ASCII.
constexpr u32 zStringGenerateHash(const char* szString) {
const char* cursor = szString;
u32 hashTally {};
while(*cursor) {
u32 signExtendedVal = *cursor;
// If the character is in the ASCII range for lowercase alphabetical letters
// make it uppercase, by XORing with 0x20 (-= 0x20 works too). If not, don't modify the value.
if((signExtendedVal - 'a') < 26)
signExtendedVal ^= 0x20;
// Do PJW hash
signExtendedVal += (hashTally << 4);
hashTally = signExtendedVal & 0xf0000000;
if(hashTally != 0)
signExtendedVal ^= (hashTally >> 24);
hashTally = signExtendedVal & ~hashTally;
// Advance to the next byte
cursor++;
}
return hashTally;
}
static_assert(zStringGenerateHash("Park.zsc") == 0x066e3a33, "zStringGenerateHash is broken");
} // namespace dmtools::dmbmx

20
lib/dmbmx/ZiffChunk.hpp Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#include <base/FourCC.hpp>
namespace dmtools::dmbmx {
/// Z-Axis IFF chunk header.
struct ZiffChunkHeader {
FourCCT ckId;
u32 ckSz;
/// returns true if the chunk id matches the specific FourCC
template<FixedString string>
constexpr bool Is() {
return ckId == FourCC<string>();
}
};
}

View File

@ -0,0 +1,85 @@
#include <cstdio>
#include <cstring>
#include <dmbmx/ZiffInputStream.hpp>
namespace dmtools::dmbmx {
ZiffInputStream::ZiffInputStream(io::Stream& stream, FourCCT expectedFormCkId)
: stream(stream), expectedFormCkId(expectedFormCkId) {
}
void ZiffInputStream::SetChunkCallback(ChunkCallback&& chunkCallback) {
this->callback = std::move(chunkCallback);
}
void ZiffInputStream::Process() {
//if(!NextChunkImpl(true))
// return;
while(!stream.Eof()) {
if(!NextChunkImpl(false))
return;
}
}
void ZiffInputStream::Rewind() {
stream.Seek(0, io::StreamSeekDirection::Beg);
}
bool ZiffInputStream::NextChunkImpl(bool isForm) {
#if 0
if(!stream.ReadObject(currentChunk.chunkHeader))
return false;
printf("FUCK: %08x %d\n", currentChunk.chunkHeader.ckId, currentChunk.chunkHeader.ckSz);
if(isForm) {
if(currentChunk.chunkHeader.ckId != expectedFormCkId) {
auto* expectedBytes = std::bit_cast<u8*>(&expectedFormCkId);
auto* gotBytes = std::bit_cast<u8*>(&currentChunk.chunkHeader.ckId);
std::printf("WARNING: Expected Form ckId '%c%c%c%c', got '%c%c%c%c'\n",
expectedBytes[0], expectedBytes[1], expectedBytes[2], expectedBytes[3],
gotBytes[0], gotBytes[1], gotBytes[2], gotBytes[3]);
}
} else {
currentChunk.chunkData.resize(sizeof(currentChunk.chunkHeader) + currentChunk.chunkHeader.ckSz);
memcpy(currentChunk.chunkData.data(), &currentChunk.chunkHeader, sizeof(currentChunk.chunkHeader));
if(stream.ReadSome(currentChunk.chunkData.data() + sizeof(currentChunk.chunkHeader), currentChunk.chunkHeader.ckSz) != currentChunk.chunkHeader.ckSz)
return false;
}
if(callback)
callback(currentChunk);
return true;
#endif
if(!stream.ReadObject(currentChunk.chunkHeader))
return false;
printf("FUCK: %08x %d\n", currentChunk.chunkHeader.ckId, currentChunk.chunkHeader.ckSz);
if(isForm) {
if(currentChunk.chunkHeader.ckId != expectedFormCkId) {
auto* expectedBytes = std::bit_cast<u8*>(&expectedFormCkId);
auto* gotBytes = std::bit_cast<u8*>(&currentChunk.chunkHeader.ckId);
std::printf("WARNING: Expected Form ckId '%c%c%c%c', got '%c%c%c%c'\n",
expectedBytes[0], expectedBytes[1], expectedBytes[2], expectedBytes[3],
gotBytes[0], gotBytes[1], gotBytes[2], gotBytes[3]);
}
}// else {
currentChunk.chunkData.resize(currentChunk.chunkHeader.ckSz);
if(stream.ReadSome(currentChunk.chunkData) != currentChunk.chunkHeader.ckSz)
return false;
//}
if(callback)
callback(currentChunk);
return true;
}
} // namespace dmtools::dmbmx

View File

@ -0,0 +1,44 @@
#include <base/io/Stream.hpp>
#include <base/Types.hpp>
#include <dmbmx/ZiffChunk.hpp>
#include <functional>
#include "base/FourCC.hpp"
namespace dmtools::dmbmx {
/// A input stream that reads a ZIFF stream and posts chunks.
struct ZiffInputStream {
/// A ZIFF chunk.
struct Chunk {
ZiffChunkHeader chunkHeader;
std::vector<u8> chunkData;
/// Casts the data to some other type.
template <class T>
T* As() {
return std::bit_cast<T*>(chunkData.data());
}
};
using ChunkCallback = std::function<void(Chunk&)>;
explicit ZiffInputStream(io::Stream& stream, FourCCT expectedFormCkId);
void SetChunkCallback(ChunkCallback&& chunkCallback);
void Process();
void Rewind();
private:
bool NextChunkImpl(bool isForm);
io::Stream& stream;
FourCCT expectedFormCkId {};
Chunk currentChunk {};
ChunkCallback callback {};
};
} // namespace dmtools::dmbmx

View File

@ -0,0 +1,10 @@
add_executable(zalgo
main.cpp
)
dmtools_target(zalgo)
target_link_libraries(zalgo PRIVATE
dmtoolsBase
dmtoolsDmBmx
)

34
progs/zalgo/main.cpp Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2024 The DMBMX2Tools Authors
// SPDX-License-Identifier: MIT
#include <base/FourCC.hpp>
#include <base/io/FileStream.hpp>
#include <base/io/MemoryStream.hpp>
#include <dmbmx/ZiffInputStream.hpp>
int main(int argc, char** argv) {
auto fs = dmtools::io::FileStream {};
if(fs.Open("/data/sda/lily/games/dmbmx2/ps2/ASSETS/LEVELS/WOODWARD/WOODWARD.ZIT", dmtools::io::FileStream::OpenMode::Read)) {
auto ziffStream = dmtools::dmbmx::ZiffInputStream { fs, dmtools::FourCC<"FSCN">() };
ziffStream.SetChunkCallback([&](dmtools::dmbmx::ZiffInputStream::Chunk& chunk) {
auto stream = dmtools::io::MemoryStream { chunk.chunkData.data(), chunk.chunkData.size() };
auto terrStream = dmtools::dmbmx::ZiffInputStream { stream, dmtools::FourCC<"TERR">() };
terrStream.SetChunkCallback([&](auto& chunk) {
auto* gotBytes = std::bit_cast<u8*>(&chunk.chunkHeader.ckId);
std::printf("inner stream ckId '%c%c%c%c' with size %d\n",
gotBytes[0], gotBytes[1], gotBytes[2], gotBytes[3], chunk.chunkHeader.ckSz);
});
terrStream.Process();
});
ziffStream.Process();
} else {
printf("Failed to open io::FileStream\n");
}
return 0;
}