Skip to content

Commit

Permalink
Merge pull request #1369 from paulsapps/api
Browse files Browse the repository at this point in the history
wip - reimport cam images (no fg1 yet)
  • Loading branch information
Paul authored Dec 5, 2021
2 parents 201b0d7 + 6c6b432 commit 910f498
Show file tree
Hide file tree
Showing 15 changed files with 393 additions and 115 deletions.
100 changes: 100 additions & 0 deletions Source/Tools/relive_api/Base64.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "Base64.hpp"

namespace ReliveAPI {
static const unsigned char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static std::string base64_encode(const u8* src, size_t len)
{
unsigned char *out, *pos;
const unsigned char *end, *in;

size_t olen = 4 * ((len + 2) / 3); /* 3-byte blocks to 4-byte */

if (olen < len)
{
return std::string(); /* integer overflow */
}

std::string outStr;
outStr.resize(olen);
out = (unsigned char*) &outStr[0];

end = src + len;
in = src;
pos = out;
while (end - in >= 3)
{
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64_table[in[2] & 0x3f];
in += 3;
}

if (end - in)
{
*pos++ = base64_table[in[0] >> 2];
if (end - in == 1)
{
*pos++ = base64_table[(in[0] & 0x03) << 4];
*pos++ = '=';
}
else
{
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
}

return outStr;
}

static const int B64index[256] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0,
0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};

static std::vector<u8> b64decode(const u8* data, const size_t len)
{
const u8* p = data;
int pad = len > 0 && (len % 4 || p[len - 1] == '=');
const size_t calcLen = ((len + 3) / 4 - pad) * 4;
std::vector<u8> str(calcLen / 4 * 3 + pad, '\0');

for (size_t i = 0, j = 0; i < calcLen; i += 4)
{
int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
str[j++] = (n >> 16) & 0xFF;
str[j++] = (n >> 8) & 0xFF;
str[j++] = n & 0xFF;
}

if (pad)
{
int n = B64index[p[calcLen]] << 18 | B64index[p[calcLen + 1]] << 12;
str[str.size() - 1] = (n >> 16) & 0xFF;

if (len > calcLen + 2 && p[calcLen + 2] != '=')
{
n |= B64index[p[calcLen + 2]] << 6;
str.push_back(n >> 8 & 0xFF);
}
}

return str;
}

std::string ToBase64(const std::vector<u8>& vec)
{
return base64_encode(vec.data(), vec.size());
}

std::vector<u8> FromBase64(const std::string& vec)
{
return b64decode(reinterpret_cast<const u8*>(vec.data()), vec.length());
}
} // namespace ReliveAPI
10 changes: 10 additions & 0 deletions Source/Tools/relive_api/Base64.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include "../../AliveLibCommon/Types.hpp"
#include <vector>
#include <string>

namespace ReliveAPI {
std::string ToBase64(const std::vector<u8>& vec);
std::vector<u8> FromBase64(const std::string& vec);
} // namespace ReliveAPI
2 changes: 2 additions & 0 deletions Source/Tools/relive_api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export(TARGETS AliveLibCommon_reliveapi FILE AliveLibCommon_reliveapi.cmake)
set_property(TARGET AliveLibCommon_reliveapi PROPERTY FOLDER "ReliveAPI")

set(relive_api_src
Base64.hpp
Base64.cpp
CamConverter.hpp
CamConverter.cpp
JsonReadUtils.hpp
Expand Down
131 changes: 90 additions & 41 deletions Source/Tools/relive_api/CamConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,13 @@ static void RGB565ToPngBuffer(const u16* camBuffer, std::vector<u8>& outPngData)
{
std::cout << "encoder error " << error << ": " << lodepng_error_text(error) << std::endl;
}
/*
else
{
lodepng::save_file(buffer, pFileName);
}
*/
}

struct FG1Buffers
{
void SetPixel(u32 layer, u32 x, u32 y, u16 pixel)
void MergePixel(u32 layer, u32 x, u32 y, u16 pixel)
{
mFg1[layer][y][x] = pixel;
mFg1[layer][y][x] |= pixel;
}
u16 mFg1[4][240][640];
};
Expand Down Expand Up @@ -93,7 +87,7 @@ class ApiFG1Reader final : public BaseFG1Reader
| (((pixel >> 10) & 31) << 0);
}

void BltRect(u32 xpos, u32 ypos, u32 width, u32 height, u32 layer, const u16* pSrcPixels, const u32* pBitMask)
void BltRectMerged(u32 xpos, u32 ypos, u32 width, u32 height, u32 layer, const u16* pSrcPixels, const u32* pBitMask)
{
mUsedLayers[layer] = true;

Expand Down Expand Up @@ -126,7 +120,7 @@ class ApiFG1Reader final : public BaseFG1Reader
}
}

mFg1Buffers->SetPixel(layer, x, y, pixelVal);
mFg1Buffers->MergePixel(layer, x, y, pixelVal);
}
}
}
Expand All @@ -144,7 +138,7 @@ class ApiFG1Reader final : public BaseFG1Reader
{
pBitMap = reinterpret_cast<const u32*>((&rChunk) + 1);
}
BltRect(rChunk.field_4_xpos_or_compressed_size,
BltRectMerged(rChunk.field_4_xpos_or_compressed_size,
rChunk.field_6_ypos,
rChunk.field_8_width + rChunk.field_4_xpos_or_compressed_size,
rChunk.field_A_height + rChunk.field_6_ypos,
Expand All @@ -155,7 +149,7 @@ class ApiFG1Reader final : public BaseFG1Reader

void OnFullChunk(const Fg1Chunk& rChunk) override
{
BltRect(rChunk.field_4_xpos_or_compressed_size,
BltRectMerged(rChunk.field_4_xpos_or_compressed_size,
rChunk.field_6_ypos,
rChunk.field_8_width + rChunk.field_4_xpos_or_compressed_size,
rChunk.field_A_height + rChunk.field_6_ypos,
Expand All @@ -177,7 +171,7 @@ class ApiFG1Reader final : public BaseFG1Reader
delete ptr;
}

void SaveLayers(CameraImageAndLayers& outData)
void LayersToPng(CameraImageAndLayers& outData)
{
for (u32 i = 0; i < 4; i++)
{
Expand All @@ -189,6 +183,29 @@ class ApiFG1Reader final : public BaseFG1Reader
}
}

static void DebugSave(const CameraImageAndLayers& outData, u32 id)
{
if (!outData.mBackgroundLayer.empty())
{
lodepng::save_file(outData.mBackgroundLayer, "bg_" + std::to_string(id) + ".png");
}

if (!outData.mForegroundLayer.empty())
{
lodepng::save_file(outData.mForegroundLayer, "fg_" + std::to_string(id) + ".png");
}

if (!outData.mBackgroundWellLayer.empty())
{
lodepng::save_file(outData.mBackgroundWellLayer, "bg_well_" + std::to_string(id) + ".png");
}

if (!outData.mForegroundWellLayer.empty())
{
lodepng::save_file(outData.mForegroundWellLayer, "fg_well_" + std::to_string(id) + ".png");
}
}

private:
std::vector<u8>& BufferForLayer(CameraImageAndLayers& outData, u32 layer)
{
Expand Down Expand Up @@ -239,7 +256,25 @@ static void AppendCamSegment(s32 x, s32 y, s32 width, s32 height, u16* pDst, con
}
}

CamConverterAO::CamConverterAO(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData)
static void MergeFG1BlocksAndConvertToPng(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData, BaseFG1Reader::FG1Format format)
{
// For some crazy reason there can be multiple FG1 blocks, here we squash them down into a single
// image for each "layer".
ApiFG1Reader reader(format);
for (u32 i = 0; i < camFile.ChunkCount(); i++)
{
if (camFile.ChunkAt(i).Header().field_8_type == ResourceManager::Resource_FG1)
{
reader.Iterate(reinterpret_cast<const FG1ResourceBlockHeader*>(camFile.ChunkAt(i).Data().data()));
}
}

reader.LayersToPng(outData);

//ApiFG1Reader::DebugSave(outData, camFile.ChunkByType(ResourceManager::Resource_Bits)->Id());
}

CamConverterAO::CamConverterAO(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData, bool processFG1)
{
std::optional<LvlFileChunk> bitsRes = camFile.ChunkByType(ResourceManager::Resource_Bits);
if (bitsRes)
Expand All @@ -257,49 +292,63 @@ CamConverterAO::CamConverterAO(const ChunkedLvlFile& camFile, CameraImageAndLaye
pIter += (slice_len / sizeof(s16));
}
RGB565ToPngBuffer(camBuffer, outData.mCameraImage);
if (processFG1)
{
MergeFG1BlocksAndConvertToPng(camFile, outData, BaseFG1Reader::FG1Format::AO);
}
}

std::optional<LvlFileChunk> fg1Res = camFile.ChunkByType(ResourceManager::Resource_FG1);
if (fg1Res)
}

static bool AEcamIsAOCam(const LvlFileChunk& bitsRes)
{
const u16* pIter = reinterpret_cast<const u16*>(bitsRes.Data().data());
for (s16 xpos = 0; xpos < 640; xpos += 16)
{
ApiFG1Reader reader(BaseFG1Reader::FG1Format::AO);
reader.Iterate(reinterpret_cast<const FG1ResourceBlockHeader*>(fg1Res->Data().data()));
reader.SaveLayers(outData);
const u16 stripSize = *pIter;
if (stripSize != (16 * 240 * sizeof(u16)))
{
return false;
}
}
return true;
}

CamConverterAE::CamConverterAE(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData)
{
std::optional<LvlFileChunk> bitsRes = camFile.ChunkByType(ResourceManager::Resource_Bits);
if (bitsRes)
{
u16 camBuffer[640 * 240] = {};
u8 vlcBuffer[0x7E00] = {};
CamDecompressor decompressor;
const u16* pIter = reinterpret_cast<const u16*>(bitsRes->Data().data());
for (s16 xpos = 0; xpos < 640; xpos += 16)
if (AEcamIsAOCam(*bitsRes))
{
const u16 stripSize = *pIter;
pIter++;
CamConverterAO aoCam(camFile, outData, false);

if (stripSize > 0)
// While its image data is AO format the FG1 is still AE format
MergeFG1BlocksAndConvertToPng(camFile, outData, BaseFG1Reader::FG1Format::AE);
}
else
{
u16 camBuffer[640 * 240] = {};
u8 vlcBuffer[0x7E00] = {};
CamDecompressor decompressor;
const u16* pIter = reinterpret_cast<const u16*>(bitsRes->Data().data());
for (s16 xpos = 0; xpos < 640; xpos += 16)
{
decompressor.vlc_decode(pIter, reinterpret_cast<u16*>(vlcBuffer));
decompressor.process_segment(reinterpret_cast<u16*>(vlcBuffer), 0);
AppendCamSegment(xpos, 0, 16, 240, camBuffer, decompressor.mDecompressedStrip);
}
const u16 stripSize = *pIter;
pIter++;

pIter += (stripSize / sizeof(u16));
}
RGB565ToPngBuffer(camBuffer, outData.mCameraImage);
}
if (stripSize > 0)
{
decompressor.vlc_decode(pIter, reinterpret_cast<u16*>(vlcBuffer));
decompressor.process_segment(reinterpret_cast<u16*>(vlcBuffer), 0);
AppendCamSegment(xpos, 0, 16, 240, camBuffer, decompressor.mDecompressedStrip);
}

std::optional<LvlFileChunk> fg1Res = camFile.ChunkByType(ResourceManager::Resource_FG1);
if (fg1Res)
{
ApiFG1Reader reader(BaseFG1Reader::FG1Format::AE);
reader.Iterate(reinterpret_cast<const FG1ResourceBlockHeader*>(fg1Res->Data().data()));
reader.SaveLayers(outData);
pIter += (stripSize / sizeof(u16));
}
RGB565ToPngBuffer(camBuffer, outData.mCameraImage);
MergeFG1BlocksAndConvertToPng(camFile, outData, BaseFG1Reader::FG1Format::AE);
}
}
}
} // namespace ReliveAPI
4 changes: 2 additions & 2 deletions Source/Tools/relive_api/CamConverter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace ReliveAPI {
class ChunkedLvlFile;

class CameraImageAndLayers
class CameraImageAndLayers final
{
public:
std::vector<u8> mCameraImage;
Expand All @@ -20,7 +20,7 @@ class CameraImageAndLayers
class CamConverterAO final
{
public:
CamConverterAO(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData);
CamConverterAO(const ChunkedLvlFile& camFile, CameraImageAndLayers& outData, bool processFG1 = true);
};

class CamConverterAE final
Expand Down
Loading

0 comments on commit 910f498

Please sign in to comment.