Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
burninrubber0 committed Feb 6, 2022
1 parent 5c71088 commit 50f655f
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "3rdparty/libbinaryio"]
path = 3rdparty/libbinaryio
url = https://github.com/burninrubber0/libbinaryio
1 change: 1 addition & 0 deletions 3rdparty/libbinaryio
Submodule libbinaryio added at 81f6a0
33 changes: 33 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 18 additions & 0 deletions include/Converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include <string>

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[]);
};
165 changes: 165 additions & 0 deletions src/Converter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include <Converter.h>

#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>

#include <binaryio/binaryreader.hpp>
#include <binaryio/binarywriter.hpp>

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 <input> <output>";
}

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<std::vector<uint8_t>>(inputSize);
in.read(reinterpret_cast<char*>(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<uint32_t>(); // Num textures or length/width
uint32_t dataOffset = reader.Read<uint32_t>();

// 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<std::array<std::array<std::array<int32_t, 24>, 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<int32_t>();
rawData->data()[i][j][k + 1] = reader.Read<int32_t>();
rawData->data()[i][j][k + 2] = reader.Read<int32_t>();
rawData->data()[i][j + 1][k] = reader.Read<int32_t>();
rawData->data()[i][j + 1][k + 1] = reader.Read<int32_t>();
rawData->data()[i][j + 1][k + 2] = reader.Read<int32_t>();
}
}
}

// Correct the out-of-order columns
for (int i = 0; i < 32; ++i) // Each texture
{
std::array<std::array<int32_t, 24>, 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<std::array<std::array<std::array<int32_t, 24>, 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<uint32_t>(size);
writer.Write<uint32_t>(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<int32_t>(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()
{

}
9 changes: 9 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <Converter.h>

#include <memory>

int main(int argc, char* argv[])
{
auto m = std::make_unique<Converter>();
return m->run(argc, argv);
}

0 comments on commit 50f655f

Please sign in to comment.