Skip to content

Commit

Permalink
xbescanner: Adds mechanism to load save game icons.
Browse files Browse the repository at this point in the history
  • Loading branch information
abaire committed Oct 2, 2021
1 parent 2936285 commit 7395885
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "3rdparty/NaturalSort"]
path = 3rdparty/NaturalSort
url = https://github.com/scopeInfinity/NaturalSort.git
[submodule "3rdparty/s3tc-dxt-decompression"]
path = 3rdparty/s3tc-dxt-decompression
url = https://github.com/Benjamin-Dobell/s3tc-dxt-decompression.git
1 change: 1 addition & 0 deletions 3rdparty/s3tc-dxt-decompression
Submodule s3tc-dxt-decompression added at 17074c
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ add_executable(${PROJECT_NAME}
Includes/settingsMenu.cpp
Includes/subAppRouter.cpp
Includes/subsystems.cpp
Includes/timing.cpp
Includes/timeMenu.cpp
Includes/timing.cpp
Includes/videoMenu.cpp
Includes/wipeCache.cpp
Includes/xbeInfo.cpp
Includes/xbeLauncher.cpp
Includes/xbeScanner.cpp
Includes/xpr0Image.cpp
3rdparty/SDL_FontCache/SDL_FontCache.c
3rdparty/s3tc-dxt-decompression/s3tc.cpp
)

set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
Expand Down
24 changes: 16 additions & 8 deletions Includes/menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ MenuXbe::MenuXbe(MenuNode* parent, std::string const& label, std::string const&
updateScanningLabel();
XBEScanner::scanPath(
remainingScanPaths.front(),
[this](bool succeeded, std::list<XBEScanner::XBEInfo> const& items,
long long duration) { this->onScanCompleted(succeeded, items, duration); });
[this](bool succeeded, std::list<XBEInfo> const& items, long long duration) {
this->onScanCompleted(succeeded, items, duration);
});
}
}

Expand Down Expand Up @@ -221,7 +222,7 @@ void MenuXbe::updateScanningLabel() {
}

void MenuXbe::onScanCompleted(bool succeeded,
std::list<XBEScanner::XBEInfo> const& items,
std::list<XBEInfo> const& items,
long long duration) {
(void)duration;
std::string path = remainingScanPaths.front();
Expand All @@ -238,8 +239,9 @@ void MenuXbe::onScanCompleted(bool succeeded,
updateScanningLabel();
XBEScanner::scanPath(
remainingScanPaths.front(),
[this](bool succeeded, std::list<XBEScanner::XBEInfo> const& items,
long long duration) { this->onScanCompleted(succeeded, items, duration); });
[this](bool succeeded, std::list<XBEInfo> const& items, long long duration) {
this->onScanCompleted(succeeded, items, duration);
});
return;
}

Expand All @@ -250,7 +252,9 @@ void MenuXbe::createChildren() {
std::vector<std::shared_ptr<MenuItem>> newChildren;

for (auto& info: discoveredItems) {
newChildren.push_back(std::make_shared<MenuLaunch>(info.name, info.path));
XPR0Image saveIcon;
info.loadCompressedSaveGameIcon(saveIcon);
newChildren.push_back(std::make_shared<MenuLaunch>(info.title, info.path, saveIcon));
}

std::sort(begin(newChildren), end(newChildren),
Expand Down Expand Up @@ -286,8 +290,12 @@ void MenuXbe::createChildren() {
/******************************************************************************************
MenuLaunch
******************************************************************************************/
MenuLaunch::MenuLaunch(std::string const& label, std::string const& path) :
MenuItem(label), path(path) {
MenuLaunch::MenuLaunch(std::string const& label, std::string path) :
MenuItem(label), path(std::move(path)), image() {
}

MenuLaunch::MenuLaunch(std::string const& label, std::string path, XPR0Image image) :
MenuItem(label), path(std::move(path)), image(std::move(image)) {
}

MenuLaunch::~MenuLaunch() {
Expand Down
11 changes: 6 additions & 5 deletions Includes/menu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "config.hpp"
#include "font.h"
#include "subApp.h"
#include "xbeInfo.h"
#include "xbeScanner.h"

class MenuNode;
Expand Down Expand Up @@ -77,14 +78,12 @@ class MenuXbe : public MenuNode {
private:
void superscroll(bool moveToPrevious);
void updateScanningLabel();
void onScanCompleted(bool succeeded,
std::list<XBEScanner::XBEInfo> const& items,
long long duration);
void onScanCompleted(bool succeeded, std::list<XBEInfo> const& items, long long duration);
void createChildren();

std::mutex childNodesLock;
std::list<std::string> remainingScanPaths;
std::vector<XBEScanner::XBEInfo> discoveredItems;
std::vector<XBEInfo> discoveredItems;

// Map of first letter to index of the first child in childNodes whose label starts with
// that letter.
Expand All @@ -93,12 +92,14 @@ class MenuXbe : public MenuNode {

class MenuLaunch : public MenuItem {
public:
MenuLaunch(std::string const& label, std::string const& path);
MenuLaunch(std::string const& label, std::string path);
MenuLaunch(std::string const& label, std::string path, XPR0Image image);
~MenuLaunch() override;
void execute(Menu*) override;

protected:
std::string path;
XPR0Image image;
};

class MenuExec : public MenuItem {
Expand Down
45 changes: 45 additions & 0 deletions Includes/xbeInfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "xbeInfo.h"
#include "infoLog.h"

XBEInfo::Icon XBEInfo::loadSaveGameIcon() const {
Icon ret;
if (saveGameXPROffset <= 0 || saveGameXPRSize <= 0) {
return ret;
}

XPR0Image compressedImage;
if (!loadCompressedSaveGameIcon(compressedImage)) {
InfoLog::outputLine("Failed to load save game icon from %s", path.c_str());
return ret;
}

if (!compressedImage.decompress(ret.imageData)) {
InfoLog::outputLine("Failed to decompress save game icon from %s", path.c_str());
return ret;
}

ret.width = compressedImage.width;
ret.height = compressedImage.height;

return ret;
}

bool XBEInfo::loadCompressedSaveGameIcon(XPR0Image& image) const {
image.clear();
FILE* xbeFile = fopen(path.c_str(), "rb");
if (!xbeFile) {
return false;
}

fseek(xbeFile, saveGameXPROffset, SEEK_SET);
std::vector<uint8_t> buffer(saveGameXPRSize);
size_t bytesRead = fread(buffer.data(), 1, saveGameXPRSize, xbeFile);
fclose(xbeFile);

if (bytesRead != saveGameXPRSize) {
InfoLog::outputLine("Failed to read save game image from %s", path.c_str());
return false;
}

return image.parse(buffer);
}
33 changes: 33 additions & 0 deletions Includes/xbeInfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef NEVOLUTIONX_XBEINFO_H
#define NEVOLUTIONX_XBEINFO_H

#include <string>
#include <vector>
#include "xpr0Image.h"

class XBEInfo {
public:
// TODO: See if the DXT1 compressed image can be used directly by the hardware instead.
struct Icon {
// imageData is always 32-bit color.
std::vector<unsigned char> imageData;
uint32_t width{ 0 };
uint32_t height{ 0 };
};

XBEInfo(std::string xbeTitle, std::string xbePath, long xprOffset, size_t xprSize) :
title(std::move(xbeTitle)), path(std::move(xbePath)), saveGameXPROffset(xprOffset),
saveGameXPRSize(xprSize) {}

Icon loadSaveGameIcon() const;
bool loadCompressedSaveGameIcon(XPR0Image& image) const;

std::string title;
std::string path;

private:
long saveGameXPROffset{ 0 };
size_t saveGameXPRSize{ 0 };
};

#endif // NEVOLUTIONX_XBEINFO_H
48 changes: 46 additions & 2 deletions Includes/xbeScanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
#define XBE_TYPE_MAGIC (0x48454258)
#define SECTORSIZE 0x1000

static bool scan(std::string const& path, std::vector<XBEScanner::XBEInfo>& ret);
#ifdef NXDK
static std::pair<DWORD, DWORD> getSaveImageFileOffset(FILE* file,
DWORD imageBase,
PXBE_SECTION_HEADER firstSectionHeader,
DWORD numberOfSections);
#endif

XBEScanner* XBEScanner::singleton = nullptr;

Expand Down Expand Up @@ -163,8 +168,47 @@ void XBEScanner::QueueItem::processFile(const std::string& xbePath) {
if (!strlen(xbeName)) {
strncpy(xbeName, findData.cFileName, sizeof(xbeName) - 1);
}

auto firstSectionHeader = reinterpret_cast<PXBE_SECTION_HEADER>(
xbeData.data() + (DWORD)xbe->PointerToSectionTable - xbe->ImageBase);
std::pair<int, int> saveImageInfo = getSaveImageFileOffset(
xbeFile, xbe->ImageBase, firstSectionHeader, xbe->NumberOfSections);

fclose(xbeFile);

results.emplace_back(xbeName, xbePath);
results.emplace_back(xbeName, xbePath, saveImageInfo.first, saveImageInfo.second);
#endif // #ifdef NXDK
}

#ifdef NXDK
// Retrieves the FileAddress and FileSize members of the "$$XTIMAGE" section, which points
// to an XPR0 compressed icon for save games.
//
// NOTE: This will seek within the given file, if it is important to maintain the current
// read position it should be saved before calling this function.
static std::pair<DWORD, DWORD> getSaveImageFileOffset(FILE* file,
DWORD imageBase,
PXBE_SECTION_HEADER firstSectionHeader,
DWORD numberOfSections) {
static const char SAVE_IMAGE_SECTION_NAME[] = "$$XTIMAGE";
static const int SECTION_NAME_SIZE = sizeof(SAVE_IMAGE_SECTION_NAME);

char nameBuffer[SECTION_NAME_SIZE] = { 0 };
for (DWORD i = 0; i < numberOfSections; ++i) {
PXBE_SECTION_HEADER header = firstSectionHeader + i;
long nameOffset = reinterpret_cast<long>(header->SectionName) - imageBase;
fseek(file, nameOffset, SEEK_SET);
size_t read_bytes = fread(nameBuffer, 1, SECTION_NAME_SIZE, file);
if (read_bytes != SECTION_NAME_SIZE) {
return std::make_pair(-1, -1);
}

if (nameBuffer[SECTION_NAME_SIZE - 1] == 0
&& !strcmp(nameBuffer, SAVE_IMAGE_SECTION_NAME)) {
return std::make_pair(header->FileAddress, header->FileSize);
}
}

return std::make_pair(-1, -1);
}
#endif // #ifdef NXDK
7 changes: 1 addition & 6 deletions Includes/xbeScanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <thread>
#include <utility>
#include <vector>
#include "xbeInfo.h"

#ifdef NXDK
#include <windows.h>
Expand All @@ -24,12 +25,6 @@
// direct subdirectories containing XBE files.
class XBEScanner {
public:
struct XBEInfo {
XBEInfo(std::string n, std::string p) : name(std::move(n)), path(std::move(p)) {}
std::string name;
std::string path;
};

// (bool succeeded, std::list<XBEInfo> const& xbes, long long scanDuration)
typedef std::function<void(bool, std::list<XBEInfo> const&, long long)> Callback;

Expand Down
50 changes: 50 additions & 0 deletions Includes/xpr0Image.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "xpr0Image.h"
#include "3rdparty/s3tc-dxt-decompression/s3tc.h"
#include "infoLog.h"

static const uint32_t XPR0_MAGIC = 0x30525058;

bool XPR0Image::parse(const std::vector<uint8_t>& buffer) {
auto& header = *reinterpret_cast<XPRHeader const*>(buffer.data());
if (header.magic != XPR0_MAGIC) {
InfoLog::outputLine("Unexpected magic bytes %X in XPR0", header.magic);
return false;
}

static const uint32_t FORMAT_MASK = 0x0000FF00;
format = header.resourceInfo.format & FORMAT_MASK;

static const uint32_t FORMAT_DXT1 = 0x00000C00;
// TODO: Investigate whether formats other than DXT1 are ever used.
if (format != FORMAT_DXT1) {
InfoLog::outputLine("Unexpected format %X (!=DXT1) in XPR0", header.resourceInfo.format);
return false;
}

uint32_t dataSize = header.totalSize - header.headerSize;
if (dataSize > buffer.size()) {
InfoLog::outputLine("Buffer size too small (%u < %u) in XPR0", buffer.size(), dataSize);
}

static const uint32_t UV_SIZE_MASK = 0x0FF00000;
static const uint32_t U_SHIFT = 20;
static const uint32_t V_SHIFT = 24;
const uint32_t sizeInfo = header.resourceInfo.format & UV_SIZE_MASK;
width = 1 << ((sizeInfo >> U_SHIFT) & 0x0F);
height = 1 << ((sizeInfo >> V_SHIFT) & 0x0F);

auto imageDataStart = buffer.cbegin() + static_cast<int>(header.headerSize);
imageData = std::vector<uint8_t>(imageDataStart, buffer.cend());

return true;
}

bool XPR0Image::decompress(std::vector<uint8_t>& output) const {
output.resize(width * height * 4);
return decompress(output.data());
}

bool XPR0Image::decompress(uint8_t* output) const {
BlockDecompressImageDXT1(width, height, imageData.data(), (unsigned long*)output);
return true;
}
47 changes: 47 additions & 0 deletions Includes/xpr0Image.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef NEVOLUTIONX_XPR0IMAGE_H
#define NEVOLUTIONX_XPR0IMAGE_H

#include <cstdint>
#include <vector>

// Encapsulates information about an XPR0 image.
class XPR0Image {
public:
struct ResourceInfo {
uint32_t common;
uint32_t data;
uint32_t lock;
uint32_t format;
uint32_t size;
};

struct XPRHeader {
uint32_t magic;
uint32_t totalSize;
uint32_t headerSize;
ResourceInfo resourceInfo;
uint32_t endOfHeader; // Should always == 0xFFFFFFFF
};

// Populates this XPR0Image from the given data buffer.
bool parse(std::vector<uint8_t> const& buffer);

// Copies 32bpp decompressed image data into the given `output` buffer.
//
// Returns true if the operation succeded, false if there was an error.
bool decompress(std::vector<uint8_t>& output) const;

bool decompress(uint8_t* output) const;

void clear() {
width = height = format = 0;
imageData.clear();
}

uint32_t width{ 0 };
uint32_t height{ 0 };
uint32_t format;
std::vector<uint8_t> imageData;
};

#endif // NEVOLUTIONX_XPR0IMAGE_H
Loading

0 comments on commit 7395885

Please sign in to comment.