add DLC package installation support
This commit is contained in:
parent
78a1b71ce4
commit
7641af3252
3 changed files with 188 additions and 0 deletions
11
README.md
11
README.md
|
|
@ -48,6 +48,17 @@ Gameplay (click to go to YouTube):
|
||||||
6. Wait for the installer to extract the game files.
|
6. Wait for the installer to extract the game files.
|
||||||
7. Click "Start Game".
|
7. Click "Start Game".
|
||||||
|
|
||||||
|
### DLC
|
||||||
|
|
||||||
|
Skate3Recomp does not include Skate 3 DLC files. To use DLC, provide package
|
||||||
|
files from your own legally obtained Xbox 360 DLC.
|
||||||
|
|
||||||
|
Create a `dlc` folder beside the executable, inside the installed game folder,
|
||||||
|
or in the Skate3Recomp user data folder. Place the DLC package files in that
|
||||||
|
folder and start the game. Skate3Recomp will validate matching Skate 3
|
||||||
|
marketplace packages and install them into the local content store
|
||||||
|
automatically.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
- Standard Xbox controls using an Xbox controller are the preferred and main input method. DualShock and others are untested, but are likely to work with Steam Input through XInput.
|
- Standard Xbox controls using an Xbox controller are the preferred and main input method. DualShock and others are untested, but are likely to work with Steam Input through XInput.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
|
|
||||||
#include <rex/cvar.h>
|
#include <rex/cvar.h>
|
||||||
#include <rex/filesystem.h>
|
#include <rex/filesystem.h>
|
||||||
|
#include <rex/filesystem/devices/stfs_container_device.h>
|
||||||
#include <rex/filesystem/devices/host_path_device.h>
|
#include <rex/filesystem/devices/host_path_device.h>
|
||||||
#include <rex/filesystem/vfs.h>
|
#include <rex/filesystem/vfs.h>
|
||||||
#include <rex/input/input_system.h>
|
#include <rex/input/input_system.h>
|
||||||
|
|
@ -45,6 +47,8 @@
|
||||||
#include <rex/ppc/context.h>
|
#include <rex/ppc/context.h>
|
||||||
#include <rex/system/function_dispatcher.h>
|
#include <rex/system/function_dispatcher.h>
|
||||||
#include <rex/system/kernel_state.h>
|
#include <rex/system/kernel_state.h>
|
||||||
|
#include <rex/system/xam/content_device.h>
|
||||||
|
#include <rex/system/xam/content_manager.h>
|
||||||
#include <rex/system/xam/user_profile.h>
|
#include <rex/system/xam/user_profile.h>
|
||||||
#include <rex/system.h>
|
#include <rex/system.h>
|
||||||
#include <rex/ui/keybinds.h>
|
#include <rex/ui/keybinds.h>
|
||||||
|
|
@ -63,6 +67,11 @@ extern const rex::PPCImageInfo eawebkit_PPCImageConfig;
|
||||||
// lift via config because they overlap with auto-discovered parent functions.
|
// lift via config because they overlap with auto-discovered parent functions.
|
||||||
extern "C" REX_FUNC(__restgprlr_19);
|
extern "C" REX_FUNC(__restgprlr_19);
|
||||||
|
|
||||||
|
REXCVAR_DEFINE_STRING(skate3_dlc_root, "", "Skate 3",
|
||||||
|
"Directory containing Skate 3 DLC package files");
|
||||||
|
REXCVAR_DEFINE_BOOL(skate3_auto_install_dlc, true, "Skate 3",
|
||||||
|
"Install DLC package files found in configured DLC folders");
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
|
@ -127,6 +136,7 @@ void SetRestartArgument(std::vector<std::string>& args, std::string name, std::s
|
||||||
|
|
||||||
constexpr std::string_view kUserDirectoryName = "skate3";
|
constexpr std::string_view kUserDirectoryName = "skate3";
|
||||||
constexpr std::string_view kSettingsFilename = "settings.toml";
|
constexpr std::string_view kSettingsFilename = "settings.toml";
|
||||||
|
constexpr std::string_view kDlcDirectoryName = "dlc";
|
||||||
|
|
||||||
std::filesystem::path DefaultDocumentsUserRoot() {
|
std::filesystem::path DefaultDocumentsUserRoot() {
|
||||||
return rex::filesystem::GetUserFolder() / std::string(kUserDirectoryName);
|
return rex::filesystem::GetUserFolder() / std::string(kUserDirectoryName);
|
||||||
|
|
@ -256,6 +266,66 @@ const char* FirstExistingFontPath(std::initializer_list<const char*> paths) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Hex8(uint32_t value) {
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream << std::uppercase << std::hex << std::setw(8) << std::setfill('0') << value;
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path InstalledMarketplaceContentPath(const std::filesystem::path& content_root,
|
||||||
|
uint32_t title_id,
|
||||||
|
const std::filesystem::path& package_path) {
|
||||||
|
return content_root / "0000000000000000" / Hex8(title_id) / "00000002" /
|
||||||
|
package_path.filename();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path InstalledMarketplaceHeaderPath(const std::filesystem::path& content_root,
|
||||||
|
uint32_t title_id,
|
||||||
|
const std::filesystem::path& package_path) {
|
||||||
|
return content_root / "0000000000000000" / Hex8(title_id) / "Headers" / "00000002" /
|
||||||
|
(package_path.filename().string() + ".header");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInstalledMarketplaceContent(const std::filesystem::path& content_root, uint32_t title_id,
|
||||||
|
const std::filesystem::path& package_path) {
|
||||||
|
return std::filesystem::is_directory(
|
||||||
|
InstalledMarketplaceContentPath(content_root, title_id, package_path)) &&
|
||||||
|
std::filesystem::is_regular_file(
|
||||||
|
InstalledMarketplaceHeaderPath(content_root, title_id, package_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> DiscoverDlcSourceDirectories(
|
||||||
|
const std::filesystem::path& executable_root, const std::filesystem::path& game_data_root,
|
||||||
|
const std::filesystem::path& user_data_root) {
|
||||||
|
std::vector<std::filesystem::path> dirs;
|
||||||
|
auto add_dir = [&](std::filesystem::path dir) {
|
||||||
|
if (dir.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::error_code ec;
|
||||||
|
dir = std::filesystem::absolute(dir, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto& existing : dirs) {
|
||||||
|
if (std::filesystem::equivalent(existing, dir, ec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ec.clear();
|
||||||
|
}
|
||||||
|
dirs.push_back(std::move(dir));
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string configured_root = REXCVAR_GET(skate3_dlc_root);
|
||||||
|
if (!configured_root.empty()) {
|
||||||
|
add_dir(configured_root);
|
||||||
|
}
|
||||||
|
add_dir(executable_root / std::string(kDlcDirectoryName));
|
||||||
|
add_dir(game_data_root / std::string(kDlcDirectoryName));
|
||||||
|
add_dir(user_data_root / std::string(kDlcDirectoryName));
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// sub_82EBAE4C: alternate entry into sub_82EBAE34 (37 vtable refs in .data).
|
// sub_82EBAE4C: alternate entry into sub_82EBAE34 (37 vtable refs in .data).
|
||||||
|
|
@ -411,6 +481,7 @@ void Skate3BaseApp::OnPostSetup() {
|
||||||
if (std::getenv("SKATE3_DISABLE_BIG_ALIASES") == nullptr) {
|
if (std::getenv("SKATE3_DISABLE_BIG_ALIASES") == nullptr) {
|
||||||
InstallBigDeviceAliases();
|
InstallBigDeviceAliases();
|
||||||
}
|
}
|
||||||
|
InstallDlcPackages();
|
||||||
InstallRecipeOverlay();
|
InstallRecipeOverlay();
|
||||||
|
|
||||||
// Register multi-entry-function alternate entries.
|
// Register multi-entry-function alternate entries.
|
||||||
|
|
@ -828,3 +899,108 @@ void Skate3BaseApp::InstallBigDeviceAliases() {
|
||||||
"dlcbig:", "\\Device\\Harddisk0\\Partition1");
|
"dlcbig:", "\\Device\\Harddisk0\\Partition1");
|
||||||
big_device_aliases_installed_ = true;
|
big_device_aliases_installed_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Skate3BaseApp::InstallDlcPackages() {
|
||||||
|
if (!REXCVAR_GET(skate3_auto_install_dlc) || !runtime() || !runtime()->kernel_state() ||
|
||||||
|
!runtime()->kernel_state()->content_manager()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t title_id = runtime()->kernel_state()->title_id();
|
||||||
|
if (title_id == 0) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 DLC install; title ID is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto user_dlc_root = runtime()->user_data_root() / std::string(kDlcDirectoryName);
|
||||||
|
std::error_code create_ec;
|
||||||
|
std::filesystem::create_directories(user_dlc_root, create_ec);
|
||||||
|
if (create_ec) {
|
||||||
|
REXLOG_WARN("Could not create Skate 3 DLC drop folder {}: {}", user_dlc_root.string(),
|
||||||
|
create_ec.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto source_dirs =
|
||||||
|
DiscoverDlcSourceDirectories(rex::filesystem::GetExecutableFolder(),
|
||||||
|
runtime()->game_data_root(), runtime()->user_data_root());
|
||||||
|
std::unordered_set<std::string> seen_packages;
|
||||||
|
size_t installed_count = 0;
|
||||||
|
size_t skipped_count = 0;
|
||||||
|
|
||||||
|
for (const auto& source_dir : source_dirs) {
|
||||||
|
if (!std::filesystem::is_directory(source_dir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code iter_ec;
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(source_dir, iter_ec)) {
|
||||||
|
if (iter_ec) {
|
||||||
|
REXLOG_WARN("Could not scan Skate 3 DLC folder {}: {}", source_dir.string(),
|
||||||
|
iter_ec.message());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry.is_regular_file()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto package_path = entry.path();
|
||||||
|
std::error_code canonical_ec;
|
||||||
|
auto package_key = std::filesystem::weakly_canonical(package_path, canonical_ec).string();
|
||||||
|
if (canonical_ec) {
|
||||||
|
package_key = std::filesystem::absolute(package_path).string();
|
||||||
|
}
|
||||||
|
if (!seen_packages.insert(package_key).second) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto header = rex::filesystem::StfsContainerDevice::ReadPackageHeader(package_path);
|
||||||
|
if (!header) {
|
||||||
|
REXLOG_WARN("Skipping DLC candidate with invalid STFS header: {}", package_path.string());
|
||||||
|
++skipped_count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto content_type =
|
||||||
|
static_cast<rex::system::XContentType>(header->metadata.content_type);
|
||||||
|
if (content_type != rex::system::XContentType::kMarketplaceContent) {
|
||||||
|
REXLOG_WARN("Skipping non-DLC content package {} with type {:08X}",
|
||||||
|
package_path.filename().string(), static_cast<uint32_t>(content_type));
|
||||||
|
++skipped_count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t package_title_id = header->metadata.execution_info.title_id;
|
||||||
|
if (package_title_id != 0 && package_title_id != title_id) {
|
||||||
|
REXLOG_WARN("Skipping DLC package {} for title {:08X}; running title is {:08X}",
|
||||||
|
package_path.filename().string(), package_title_id, title_id);
|
||||||
|
++skipped_count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsInstalledMarketplaceContent(runtime()->user_data_root(), title_id, package_path)) {
|
||||||
|
REXLOG_INFO("DLC package already installed: {}", package_path.filename().string());
|
||||||
|
++skipped_count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
REXLOG_INFO("Installing Skate 3 DLC package: {}", package_path.string());
|
||||||
|
const auto result = runtime()->kernel_state()->content_manager()->InstallContent(package_path);
|
||||||
|
if (result == 0) {
|
||||||
|
++installed_count;
|
||||||
|
REXLOG_INFO("Installed Skate 3 DLC package: {}", package_path.filename().string());
|
||||||
|
} else {
|
||||||
|
++skipped_count;
|
||||||
|
REXLOG_WARN("Failed to install Skate 3 DLC package {}: {:08X}",
|
||||||
|
package_path.filename().string(), static_cast<uint32_t>(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installed_count || skipped_count) {
|
||||||
|
REXLOG_INFO("Skate 3 DLC scan complete: {} installed, {} skipped", installed_count,
|
||||||
|
skipped_count);
|
||||||
|
} else {
|
||||||
|
REXLOG_INFO("No Skate 3 DLC packages found. Drop legally obtained DLC package files in {}",
|
||||||
|
user_dlc_root.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
private:
|
private:
|
||||||
void InstallRecipeOverlay();
|
void InstallRecipeOverlay();
|
||||||
void InstallBigDeviceAliases();
|
void InstallBigDeviceAliases();
|
||||||
|
void InstallDlcPackages();
|
||||||
void ToggleSimpleSettings();
|
void ToggleSimpleSettings();
|
||||||
void ApplySettingsCursorMode();
|
void ApplySettingsCursorMode();
|
||||||
void ApplyGameplayCursorMode();
|
void ApplyGameplayCursorMode();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue