From 50f655f9e5229cb6058d554516a529b944361ca2 Mon Sep 17 00:00:00 2001 From: burninrubber0 Date: Sat, 5 Feb 2022 23:01:30 -0500 Subject: [PATCH] First version --- .gitignore | 1 + .gitmodules | 3 + 3rdparty/libbinaryio | 1 + CMakeLists.txt | 33 +++++++++ README.md | 8 ++- include/Converter.h | 18 +++++ src/Converter.cpp | 165 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 9 +++ 8 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 160000 3rdparty/libbinaryio create mode 100644 CMakeLists.txt create mode 100644 include/Converter.h create mode 100644 src/Converter.cpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d71eef9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "3rdparty/libbinaryio"] + path = 3rdparty/libbinaryio + url = https://github.com/burninrubber0/libbinaryio diff --git a/3rdparty/libbinaryio b/3rdparty/libbinaryio new file mode 160000 index 0000000..81f6a01 --- /dev/null +++ b/3rdparty/libbinaryio @@ -0,0 +1 @@ +Subproject commit 81f6a01004d3bbc5b937a6889676c065dfd5e960 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f87289a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.14) +project(ColourCube_Converter CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(ROOT ${CMAKE_CURRENT_SOURCE_DIR}) + +set(SOURCES + ${SOURCES} + src/main.cpp + src/Converter.cpp + ) + +set(HEADERS + ${HEADERS} + include/Converter.h + ) + +add_executable(ColourCube_Converter ${SOURCES} ${HEADERS}) + +# libbinaryio +set(LIBBINARYIO_ROOT ${ROOT}\\3rdparty\\libbinaryio) +add_subdirectory(3rdparty/libbinaryio "${CMAKE_CURRENT_BINARY_DIR}/3rdparty/libbinaryio" EXCLUDE_FROM_ALL) +target_include_directories(ColourCube_Converter PRIVATE ${LIBBINARYIO_ROOT} "${CMAKE_CURRENT_BINARY_DIR}/3rdparty/libbinaryio") + +# Triggers to glTF +target_include_directories(ColourCube_Converter PRIVATE "${ROOT}/include") + +target_link_libraries(ColourCube_Converter PRIVATE libbinaryio) + +# VS stuff +set_property(DIRECTORY ${ROOT} PROPERTY VS_STARTUP_PROJECT ColourCube_Converter) +source_group(TREE ${ROOT} FILES ${SOURCES} ${HEADERS}) \ No newline at end of file diff --git a/README.md b/README.md index 65c95f2..fb6cb5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ -# BP_ColourCube_Converter - Converts ColourCube resources from Burnout Paradise from one platform to another. +# ColourCube Converter +Converts ColourCube resources from Burnout Paradise on Xbox 360 to PC (RGB24). + +ColourCubes are a set of 32 32x32 textures used for lighting/tint (may be displayed as one 32x1024 texture). Changing them significantly alters the way the game looks. + +There might be an easier way to convert between platforms, I just landed on this implementation by comparing the Xbox and PC resources. Output files should also work for PS4 by moving the data pointer from offset 4 to 8. PS3 and NX support may be added later as it's more or less the same deal, they're all RGB24, just some have their data moved around. \ No newline at end of file diff --git a/include/Converter.h b/include/Converter.h new file mode 100644 index 0000000..1515dc1 --- /dev/null +++ b/include/Converter.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class Converter +{ + std::string input; + std::string output; + + void showHelp(); + int getArgs(int argc, char* argv[]); + int convert(); + +public: + Converter(); + + int run(int argc, char* argv[]); +}; \ No newline at end of file diff --git a/src/Converter.cpp b/src/Converter.cpp new file mode 100644 index 0000000..77de2cd --- /dev/null +++ b/src/Converter.cpp @@ -0,0 +1,165 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +int Converter::run(int argc, char* argv[]) +{ + if (getArgs(argc, argv)) + return 1; + + return convert(); +} + +void Converter::showHelp() +{ + std::cout << "ColourCube Converter v1.0 by burninrubber0\n" + << "Converts Xbox 360 ColourCube resources to PC (RGB24).\n" + << "Usage: ColourCube_Converter "; +} + +int Converter::getArgs(int argc, char* argv[]) +{ + if (argc != 3) + { + showHelp(); + return 1; + } + + input = argv[1]; + output = argv[2]; + + return 0; +} + +int Converter::convert() +{ + // Create the input reader + std::ifstream in(input, std::ios::in | std::ios::binary); + if (in.fail()) + return 1; + + size_t inputSize = std::filesystem::file_size(input); + const auto& buffer = std::make_shared>(inputSize); + in.read(reinterpret_cast(buffer->data()), inputSize); + binaryio::BinaryReader reader(buffer); + reader.Set64BitMode(false); // TODO: PS4 probably has the offset as 64-bit + reader.SetBigEndian(true); // All files are effectively big endian + + in.close(); + + // Read the header + reader.Seek(0); + uint32_t size = reader.Read(); // Num textures or length/width + uint32_t dataOffset = reader.Read(); + + // Read the data and convert it to standard RGB24 + // One array of 24 ints is a single column (or row?) of 32 pixels + auto rawData = std::make_unique, 32>, 32>>(); + + // Convert the pixel arrays to RGB24 + reader.Seek(dataOffset); + // For each of the 32 textures (0xC00) + for (int i = 0; i < 32; ++i) + { + // For each of the 16 2-pixel wide interleaved columns (0xC0) + for (int j = 0; j < 32; j += 2) + { + // Read 4 pixels of column 1, then 4 of column 2 (0xC[2]) + for (int k = 0; k < 24; k += 3) + { + rawData->data()[i][j][k] = reader.Read(); + rawData->data()[i][j][k + 1] = reader.Read(); + rawData->data()[i][j][k + 2] = reader.Read(); + rawData->data()[i][j + 1][k] = reader.Read(); + rawData->data()[i][j + 1][k + 1] = reader.Read(); + rawData->data()[i][j + 1][k + 2] = reader.Read(); + } + } + } + + // Correct the out-of-order columns + for (int i = 0; i < 32; ++i) // Each texture + { + std::array, 32> rawDataFixed = rawData->data()[i]; + // Apply this change to the latter quarters of each 0xC00 + for (int j = 16; j < 32; ++j) // Each 0x60 block in the 2nd half + { + // Left pixels become right, right pixels become left + for (int k = 0; k < 12; ++k) + rawDataFixed.data()[j][k] = rawData->data()[i][j][k + 12]; + for (int k = 12; k < 24; ++k) + rawDataFixed.data()[j][k] = rawData->data()[i][j][k - 12]; + } + rawData->data()[i] = rawDataFixed; + } + + // Correct out-of-order rows + for (int i = 0; i < 4; ++i) // Pattern occurs 4 times, or 8 counting reverse + { + auto rawDataFixed = std::make_unique, 32>, 32>>(*rawData); + // Copy 0x300-long chunks to the correct positions + for (int j = 0; j < 8; ++j) + { + rawDataFixed->data()[i * 8][j + 8] = rawData->data()[i * 8][j + 16]; // Copy 0x600 to 0x300 + rawDataFixed->data()[i * 8][j + 16] = rawData->data()[i * 8 + 2][j]; // 0x1800 to 0x600 + rawDataFixed->data()[i * 8][j + 24] = rawData->data()[i * 8 + 2][j + 16]; // 0x1E00 to 0x900 + rawDataFixed->data()[i * 8 + 1][j] = rawData->data()[i * 8][j + 8]; // 0x300 to 0xC00 + rawDataFixed->data()[i * 8 + 1][j + 8] = rawData->data()[i * 8][j + 24]; // 0x900 to 0xF00 + rawDataFixed->data()[i * 8 + 1][j + 16] = rawData->data()[i * 8 + 2][j + 8]; // 0x1B00 to 0x1200 + rawDataFixed->data()[i * 8 + 1][j + 24] = rawData->data()[i * 8 + 2][j + 24]; // 0x2100 to 0x1500 + rawDataFixed->data()[i * 8 + 2][j] = rawData->data()[i * 8 + 1][j]; // 0xC00 to 0x1800 + rawDataFixed->data()[i * 8 + 2][j + 8] = rawData->data()[i * 8 + 1][j + 16]; // 0x1200 to 0x1B00 + rawDataFixed->data()[i * 8 + 2][j + 16] = rawData->data()[i * 8 + 3][j]; // 0x2400 to 0x1E00 + rawDataFixed->data()[i * 8 + 2][j + 24] = rawData->data()[i * 8 + 3][j + 16]; // 0x2A00 to 0x2100 + rawDataFixed->data()[i * 8 + 3][j] = rawData->data()[i * 8 + 1][j + 8]; // 0xF00 to 0x2400 + rawDataFixed->data()[i * 8 + 3][j + 8] = rawData->data()[i * 8 + 1][j + 24]; // 0x1500 to 0x2700 + rawDataFixed->data()[i * 8 + 3][j + 16] = rawData->data()[i * 8 + 3][j + 8]; // 0x2700 to 0x2A00 + rawDataFixed->data()[i * 8 + 4][j] = rawData->data()[i * 8 + 4][j + 16]; // 0x3600 to 0x3000 + rawDataFixed->data()[i * 8 + 4][j + 8] = rawData->data()[i * 8 + 4][j]; // 0x3000 to 0x3300 + rawDataFixed->data()[i * 8 + 4][j + 16] = rawData->data()[i * 8 + 6][j + 16]; // 0x4E00 to 0x3600 + rawDataFixed->data()[i * 8 + 4][j + 24] = rawData->data()[i * 8 + 6][j]; // 0x4800 to 0x3900 + rawDataFixed->data()[i * 8 + 5][j] = rawData->data()[i * 8 + 4][j + 24]; // 0x3900 to 0x3C00 + rawDataFixed->data()[i * 8 + 5][j + 8] = rawData->data()[i * 8 + 4][j + 8]; // 0x3300 to 0x3F00 + rawDataFixed->data()[i * 8 + 5][j + 16] = rawData->data()[i * 8 + 6][j + 24]; // 0x5100 to 0x4200 + rawDataFixed->data()[i * 8 + 5][j + 24] = rawData->data()[i * 8 + 6][j + 8]; // 0x4B00 to 0x4500 + rawDataFixed->data()[i * 8 + 6][j] = rawData->data()[i * 8 + 5][j + 16]; // 0x4200 to 0x4800 + rawDataFixed->data()[i * 8 + 6][j + 8] = rawData->data()[i * 8 + 5][j]; // 0x3C00 to 0x4B00 + rawDataFixed->data()[i * 8 + 6][j + 16] = rawData->data()[i * 8 + 7][j + 16]; // 0x5A00 to 0x4E00 + rawDataFixed->data()[i * 8 + 6][j + 24] = rawData->data()[i * 8 + 7][j]; // 0x5400 to 0x5100 + rawDataFixed->data()[i * 8 + 7][j] = rawData->data()[i * 8 + 5][j + 24]; // 0x4500 to 0x5400 + rawDataFixed->data()[i * 8 + 7][j + 8] = rawData->data()[i * 8 + 5][j + 8]; // 0x3F00 to 0x5700 + rawDataFixed->data()[i * 8 + 7][j + 16] = rawData->data()[i * 8 + 7][j + 24]; // 0x5D00 to 0x5A00 + rawDataFixed->data()[i * 8 + 7][j + 24] = rawData->data()[i * 8 + 7][j + 8]; // 0x5700 to 0x5D00 + } + *rawData = *rawDataFixed; + } + + // Write to buffer + binaryio::BinaryWriter writer; + writer.Write(size); + writer.Write(dataOffset); + writer.Seek(dataOffset); + writer.SetBigEndian(true); + for (int i = 0; i < 32; ++i) + for (int j = 0; j < 32; ++j) + for (int k = 0; k < 24; ++k) + writer.Write(rawData->data()[i][j][k]); + + std::ofstream out(output, std::ios::out | std::ios::binary); + out << writer.GetStream().rdbuf(); + out.close(); + + return 0; +} + +Converter::Converter() +{ + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..25b7a5e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,9 @@ +#include + +#include + +int main(int argc, char* argv[]) +{ + auto m = std::make_unique(); + return m->run(argc, argv); +} \ No newline at end of file