Skip to content

Commit 50f2929

Browse files
committed
xbescanner: Adds mechanism to load save game icons.
1 parent c936b6d commit 50f2929

File tree

13 files changed

+266
-24
lines changed

13 files changed

+266
-24
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "3rdparty/NaturalSort"]
55
path = 3rdparty/NaturalSort
66
url = https://github.com/scopeInfinity/NaturalSort.git
7+
[submodule "3rdparty/s3tc-dxt-decompression"]
8+
path = 3rdparty/s3tc-dxt-decompression
9+
url = https://github.com/Benjamin-Dobell/s3tc-dxt-decompression.git

3rdparty/s3tc-dxt-decompression

Submodule s3tc-dxt-decompression added at 17074c2

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ add_executable(${PROJECT_NAME}
3535
Includes/sntpClient.cpp
3636
Includes/subAppRouter.cpp
3737
Includes/subsystems.cpp
38-
Includes/timing.cpp
3938
Includes/timeMenu.cpp
39+
Includes/timing.cpp
4040
Includes/videoMenu.cpp
4141
Includes/wipeCache.cpp
42+
Includes/xbeInfo.cpp
4243
Includes/xbeLauncher.cpp
4344
Includes/xbeScanner.cpp
45+
Includes/xpr0Image.cpp
4446
3rdparty/SDL_FontCache/SDL_FontCache.c
47+
3rdparty/s3tc-dxt-decompression/s3tc.cpp
4548
)
4649

4750
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)

Includes/menu.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,9 @@ MenuXbe::MenuXbe(MenuNode* parent, std::string const& label, std::string const&
157157
updateScanningLabel();
158158
XBEScanner::scanPath(
159159
remainingScanPaths.front(),
160-
[this](bool succeeded, std::list<XBEScanner::XBEInfo> const& items,
161-
long long duration) { this->onScanCompleted(succeeded, items, duration); });
160+
[this](bool succeeded, std::list<XBEInfo> const& items, long long duration) {
161+
this->onScanCompleted(succeeded, items, duration);
162+
});
162163
}
163164
}
164165

@@ -221,7 +222,7 @@ void MenuXbe::updateScanningLabel() {
221222
}
222223

223224
void MenuXbe::onScanCompleted(bool succeeded,
224-
std::list<XBEScanner::XBEInfo> const& items,
225+
std::list<XBEInfo> const& items,
225226
long long duration) {
226227
(void)duration;
227228
std::string path = remainingScanPaths.front();
@@ -239,8 +240,9 @@ void MenuXbe::onScanCompleted(bool succeeded,
239240
updateScanningLabel();
240241
XBEScanner::scanPath(
241242
remainingScanPaths.front(),
242-
[this](bool succeeded, std::list<XBEScanner::XBEInfo> const& items,
243-
long long duration) { this->onScanCompleted(succeeded, items, duration); });
243+
[this](bool succeeded, std::list<XBEInfo> const& items, long long duration) {
244+
this->onScanCompleted(succeeded, items, duration);
245+
});
244246
return;
245247
}
246248

@@ -251,7 +253,9 @@ void MenuXbe::createChildren() {
251253
std::vector<std::shared_ptr<MenuItem>> newChildren;
252254

253255
for (auto& info: discoveredItems) {
254-
newChildren.push_back(std::make_shared<MenuLaunch>(info.name, info.path));
256+
XPR0Image saveIcon;
257+
info.loadCompressedSaveGameIcon(saveIcon);
258+
newChildren.push_back(std::make_shared<MenuLaunch>(info.title, info.path, saveIcon));
255259
}
256260

257261
std::sort(begin(newChildren), end(newChildren),
@@ -288,8 +292,12 @@ void MenuXbe::createChildren() {
288292
/******************************************************************************************
289293
MenuLaunch
290294
******************************************************************************************/
291-
MenuLaunch::MenuLaunch(std::string const& label, std::string const& path) :
292-
MenuItem(label), path(path) {
295+
MenuLaunch::MenuLaunch(std::string const& label, std::string path) :
296+
MenuItem(label), path(std::move(path)), image() {
297+
}
298+
299+
MenuLaunch::MenuLaunch(std::string const& label, std::string path, XPR0Image image) :
300+
MenuItem(label), path(std::move(path)), image(std::move(image)) {
293301
}
294302

295303
MenuLaunch::~MenuLaunch() {

Includes/menu.hpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "config.hpp"
1010
#include "font.h"
1111
#include "subApp.h"
12+
#include "xbeInfo.h"
1213
#include "xbeScanner.h"
1314

1415
class MenuNode;
@@ -77,14 +78,12 @@ class MenuXbe : public MenuNode {
7778
private:
7879
void superscroll(bool moveToPrevious);
7980
void updateScanningLabel();
80-
void onScanCompleted(bool succeeded,
81-
std::list<XBEScanner::XBEInfo> const& items,
82-
long long duration);
81+
void onScanCompleted(bool succeeded, std::list<XBEInfo> const& items, long long duration);
8382
void createChildren();
8483

8584
std::mutex childNodesLock;
8685
std::list<std::string> remainingScanPaths;
87-
std::vector<XBEScanner::XBEInfo> discoveredItems;
86+
std::vector<XBEInfo> discoveredItems;
8887

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

9493
class MenuLaunch : public MenuItem {
9594
public:
96-
MenuLaunch(std::string const& label, std::string const& path);
95+
MenuLaunch(std::string const& label, std::string path);
96+
MenuLaunch(std::string const& label, std::string path, XPR0Image image);
9797
~MenuLaunch() override;
9898
void execute(Menu*) override;
9999

100100
protected:
101101
std::string path;
102+
XPR0Image image;
102103
};
103104

104105
class MenuExec : public MenuItem {

Includes/sntpClient.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ void sntpClient::updateTime() const {
7474
message.originTimestamp.fractionalSeconds =
7575
htonl(message.originTimestamp.fractionalSeconds);
7676
if (message.originTimestamp.seconds || message.originTimestamp.fractionalSeconds) {
77-
InfoLog::outputLine(InfoLog::INFO, "SNTP: Origin epoch is not 0: %lld\n", message.originTimestamp);
77+
InfoLog::outputLine(InfoLog::INFO, "SNTP: Origin epoch is not 0: %lld\n",
78+
message.originTimestamp);
7879
}
7980

8081
message.transmitTimestamp.seconds = htonl(message.transmitTimestamp.seconds);
@@ -95,7 +96,8 @@ void sntpClient::updateTime() const {
9596
}
9697

9798
if (delta > allowedDriftSeconds) {
98-
InfoLog::outputLine(InfoLog::DEBUG, "SNTP: Updating system clock (%llu seconds of drift)\n", delta);
99+
InfoLog::outputLine(InfoLog::DEBUG,
100+
"SNTP: Updating system clock (%llu seconds of drift)\n", delta);
99101
NTSTATUS status = NtSetSystemTime(&serverTime, nullptr);
100102
if (!NT_SUCCESS(status)) {
101103
InfoLog::outputLine(InfoLog::INFO, "SNTP: NtSetSystemTime failed: %X\n", status);

Includes/xbeInfo.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "xbeInfo.h"
2+
#include "infoLog.h"
3+
4+
XBEInfo::Icon XBEInfo::loadSaveGameIcon() const {
5+
Icon ret;
6+
if (saveGameXPROffset <= 0 || saveGameXPRSize <= 0) {
7+
return ret;
8+
}
9+
10+
XPR0Image compressedImage;
11+
if (!loadCompressedSaveGameIcon(compressedImage)) {
12+
InfoLog::outputLine(InfoLog::WARNING, "Failed to load save game icon from %s",
13+
path.c_str());
14+
return ret;
15+
}
16+
17+
if (!compressedImage.decompress(ret.imageData)) {
18+
InfoLog::outputLine(InfoLog::WARNING, "Failed to decompress save game icon from %s",
19+
path.c_str());
20+
return ret;
21+
}
22+
23+
ret.width = compressedImage.width;
24+
ret.height = compressedImage.height;
25+
26+
return ret;
27+
}
28+
29+
bool XBEInfo::loadCompressedSaveGameIcon(XPR0Image& image) const {
30+
image.clear();
31+
FILE* xbeFile = fopen(path.c_str(), "rb");
32+
if (!xbeFile) {
33+
return false;
34+
}
35+
36+
fseek(xbeFile, saveGameXPROffset, SEEK_SET);
37+
std::vector<uint8_t> buffer(saveGameXPRSize);
38+
size_t bytesRead = fread(buffer.data(), 1, saveGameXPRSize, xbeFile);
39+
fclose(xbeFile);
40+
41+
if (bytesRead != saveGameXPRSize) {
42+
InfoLog::outputLine(InfoLog::WARNING, "Failed to read save game image from %s",
43+
path.c_str());
44+
return false;
45+
}
46+
47+
return image.parse(buffer);
48+
}

Includes/xbeInfo.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef NEVOLUTIONX_XBEINFO_H
2+
#define NEVOLUTIONX_XBEINFO_H
3+
4+
#include <string>
5+
#include <vector>
6+
#include "xpr0Image.h"
7+
8+
class XBEInfo {
9+
public:
10+
// TODO: See if the DXT1 compressed image can be used directly by the hardware instead.
11+
struct Icon {
12+
// imageData is always 32-bit color.
13+
std::vector<unsigned char> imageData;
14+
uint32_t width{ 0 };
15+
uint32_t height{ 0 };
16+
};
17+
18+
XBEInfo(std::string xbeTitle, std::string xbePath, long xprOffset, size_t xprSize) :
19+
title(std::move(xbeTitle)), path(std::move(xbePath)), saveGameXPROffset(xprOffset),
20+
saveGameXPRSize(xprSize) {}
21+
22+
Icon loadSaveGameIcon() const;
23+
bool loadCompressedSaveGameIcon(XPR0Image& image) const;
24+
25+
std::string title;
26+
std::string path;
27+
28+
private:
29+
long saveGameXPROffset{ 0 };
30+
size_t saveGameXPRSize{ 0 };
31+
};
32+
33+
#endif // NEVOLUTIONX_XBEINFO_H

Includes/xbeScanner.cpp

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
#define XBE_TYPE_MAGIC (0x48454258)
1212
#define SECTORSIZE 0x1000
1313

14+
#ifdef NXDK
15+
static std::pair<DWORD, DWORD> getSaveImageFileOffset(FILE* file,
16+
DWORD imageBase,
17+
PXBE_SECTION_HEADER firstSectionHeader,
18+
DWORD numberOfSections);
19+
#endif
20+
1421
XBEScanner* XBEScanner::singleton = nullptr;
1522

1623
XBEScanner* XBEScanner::getInstance() {
@@ -165,8 +172,47 @@ void XBEScanner::QueueItem::processFile(const std::string& xbePath) {
165172
if (!strlen(xbeName)) {
166173
strncpy(xbeName, findData.cFileName, sizeof(xbeName) - 1);
167174
}
175+
176+
auto firstSectionHeader = reinterpret_cast<PXBE_SECTION_HEADER>(
177+
xbeData.data() + (DWORD)xbe->PointerToSectionTable - xbe->ImageBase);
178+
std::pair<int, int> saveImageInfo = getSaveImageFileOffset(
179+
xbeFile, xbe->ImageBase, firstSectionHeader, xbe->NumberOfSections);
180+
168181
fclose(xbeFile);
169182

170-
results.emplace_back(xbeName, xbePath);
183+
results.emplace_back(xbeName, xbePath, saveImageInfo.first, saveImageInfo.second);
171184
#endif // #ifdef NXDK
172185
}
186+
187+
#ifdef NXDK
188+
// Retrieves the FileAddress and FileSize members of the "$$XTIMAGE" section, which points
189+
// to an XPR0 compressed icon for save games.
190+
//
191+
// NOTE: This will seek within the given file, if it is important to maintain the current
192+
// read position it should be saved before calling this function.
193+
static std::pair<DWORD, DWORD> getSaveImageFileOffset(FILE* file,
194+
DWORD imageBase,
195+
PXBE_SECTION_HEADER firstSectionHeader,
196+
DWORD numberOfSections) {
197+
static const char SAVE_IMAGE_SECTION_NAME[] = "$$XTIMAGE";
198+
static const int SECTION_NAME_SIZE = sizeof(SAVE_IMAGE_SECTION_NAME);
199+
200+
char nameBuffer[SECTION_NAME_SIZE] = { 0 };
201+
for (DWORD i = 0; i < numberOfSections; ++i) {
202+
PXBE_SECTION_HEADER header = firstSectionHeader + i;
203+
long nameOffset = reinterpret_cast<long>(header->SectionName) - imageBase;
204+
fseek(file, nameOffset, SEEK_SET);
205+
size_t read_bytes = fread(nameBuffer, 1, SECTION_NAME_SIZE, file);
206+
if (read_bytes != SECTION_NAME_SIZE) {
207+
return std::make_pair(-1, -1);
208+
}
209+
210+
if (nameBuffer[SECTION_NAME_SIZE - 1] == 0
211+
&& !strcmp(nameBuffer, SAVE_IMAGE_SECTION_NAME)) {
212+
return std::make_pair(header->FileAddress, header->FileSize);
213+
}
214+
}
215+
216+
return std::make_pair(-1, -1);
217+
}
218+
#endif // #ifdef NXDK

Includes/xbeScanner.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <thread>
1010
#include <utility>
1111
#include <vector>
12+
#include "xbeInfo.h"
1213

1314
#ifdef NXDK
1415
#include <windows.h>
@@ -24,12 +25,6 @@
2425
// direct subdirectories containing XBE files.
2526
class XBEScanner {
2627
public:
27-
struct XBEInfo {
28-
XBEInfo(std::string n, std::string p) : name(std::move(n)), path(std::move(p)) {}
29-
std::string name;
30-
std::string path;
31-
};
32-
3328
// (bool succeeded, std::list<XBEInfo> const& xbes, long long scanDuration)
3429
typedef std::function<void(bool, std::list<XBEInfo> const&, long long)> Callback;
3530

0 commit comments

Comments
 (0)