mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-12 01:38:50 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88b990b060 | |||
| e0e362bd99 | |||
| 5654d8f21b | |||
| 730e3122d5 | |||
| f424536d3b | |||
| 2d9dc4e0f2 |
@@ -281,7 +281,7 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo
|
||||
|
||||
$manifest = [ordered]@{
|
||||
SchemaVersion = 2
|
||||
ProductVersion = '0.2.29'
|
||||
ProductVersion = '0.2.31'
|
||||
ExpectedGameId = $pins.GameId
|
||||
ExpectedDolSha256 = $pins.DolSha256
|
||||
ExpectedRelSha256 = $pins.RelSha256
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WiiCompiled.Setup.Common.Cli</RootNamespace>
|
||||
<AssemblyName>WiiCompiled.Setup.Common.Cli</AssemblyName>
|
||||
<Version>0.2.29</Version>
|
||||
<Version>0.2.31</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WiiCompiled.Setup.Common</RootNamespace>
|
||||
<AssemblyName>WiiCompiled.Setup.Common</AssemblyName>
|
||||
<Version>0.2.29</Version>
|
||||
<Version>0.2.31</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers</Description>
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace WiiCompiled.Setup.Linux;
|
||||
internal static class ProductInfo
|
||||
{
|
||||
public const string Name = "WiiCompiled";
|
||||
public const string Version = "0.2.29";
|
||||
public const string Version = "0.2.31";
|
||||
}
|
||||
|
||||
/// <summary>One installed product's record inside install-state.json.</summary>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>WiiCompiled.Setup.Linux</AssemblyName>
|
||||
<RootNamespace>WiiCompiled.Setup.Linux</RootNamespace>
|
||||
<Version>0.2.29</Version>
|
||||
<Version>0.2.31</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Command-line installer and launcher for WiiCompiled on Linux</Description>
|
||||
|
||||
@@ -121,7 +121,7 @@ internal static class PlatformChecks
|
||||
internal static class ProductInfo
|
||||
{
|
||||
public const string Name = "WiiCompiled";
|
||||
public const string Version = "0.2.29";
|
||||
public const string Version = "0.2.31";
|
||||
|
||||
/// <summary>
|
||||
/// The setup executable is copied into the installation under this name. It is the launcher and
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<AssemblyName>WiiCompiled.Setup</AssemblyName>
|
||||
<RootNamespace>WiiCompiled.Setup.Windows</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Version>0.2.29</Version>
|
||||
<Version>0.2.31</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Command-line installer and launcher for WiiCompiled</Description>
|
||||
|
||||
@@ -277,6 +277,11 @@ target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform)
|
||||
target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests)
|
||||
|
||||
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
|
||||
target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests)
|
||||
|
||||
add_executable(mkw_nand_settings_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_settings_tests.cpp")
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(mkw_nand_settings_tests PRIVATE Threads::Threads)
|
||||
@@ -284,10 +289,10 @@ target_include_directories(mkw_nand_settings_tests PRIVATE "${CMAKE_CURRENT_LIST
|
||||
target_compile_features(mkw_nand_settings_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_nand_settings_tests COMMAND mkw_nand_settings_tests)
|
||||
|
||||
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
|
||||
target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests)
|
||||
add_executable(mkw_sc_serial_tests "${CMAKE_CURRENT_LIST_DIR}/tests/sc_serial_tests.cpp")
|
||||
target_include_directories(mkw_sc_serial_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
target_compile_features(mkw_sc_serial_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_sc_serial_tests COMMAND mkw_sc_serial_tests)
|
||||
|
||||
# The input expression engine is self-contained, so it can be exercised without
|
||||
# linking the runtime or SDL.
|
||||
|
||||
@@ -129,6 +129,20 @@ inline std::optional<std::array<uint8_t, 256>> EncodeNew(const std::string& seri
|
||||
return bytes; // The unused tail stays raw zero, as in Dolphin.
|
||||
}
|
||||
|
||||
// Atomically claim our own scratch directory. A collision belongs to another
|
||||
// launch (or a previous crashed launch); leave it untouched and try another name.
|
||||
inline std::optional<std::filesystem::path> CreateScratchDirectory(
|
||||
const std::filesystem::path& parent, const std::string& token, std::error_code& ec) {
|
||||
for (unsigned attempt = 0; attempt < 128; ++attempt) {
|
||||
const auto candidate = parent / (".setting-init-" + token + "-" + std::to_string(attempt));
|
||||
ec.clear();
|
||||
if (std::filesystem::create_directory(candidate, ec)) return candidate;
|
||||
if (ec && ec != std::errc::file_exists) return std::nullopt;
|
||||
}
|
||||
ec = std::make_error_code(std::errc::file_exists);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Never replace an existing file, including an unreadable or damaged one.
|
||||
// Publish a complete file atomically so simultaneous launches use one identity.
|
||||
inline bool Ensure(const std::filesystem::path& root, std::string& error,
|
||||
@@ -161,13 +175,20 @@ inline bool Ensure(const std::filesystem::path& root, std::string& error,
|
||||
return false;
|
||||
}
|
||||
static std::atomic<unsigned> sequence{0};
|
||||
const auto scratch = path.parent_path() / (".setting-init-" + std::to_string(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count()) + "-" + std::to_string(sequence++));
|
||||
if (!std::filesystem::create_directory(scratch, ec)) {
|
||||
#ifdef _WIN32
|
||||
const auto processId = GetCurrentProcessId();
|
||||
#else
|
||||
const auto processId = getpid();
|
||||
#endif
|
||||
const auto scratch = CreateScratchDirectory(path.parent_path(),
|
||||
std::to_string(processId) + "-" + std::to_string(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count()) + "-" +
|
||||
std::to_string(sequence++), ec);
|
||||
if (!scratch) {
|
||||
error = "Cannot create temporary NAND settings directory: " + ec.message();
|
||||
return false;
|
||||
}
|
||||
const auto temporary = scratch / "setting.txt";
|
||||
const auto temporary = *scratch / "setting.txt";
|
||||
bool written = false;
|
||||
{
|
||||
std::ofstream output(temporary, std::ios::binary);
|
||||
@@ -184,7 +205,7 @@ inline bool Ensure(const std::filesystem::path& root, std::string& error,
|
||||
#endif
|
||||
}
|
||||
std::filesystem::remove(temporary, ec);
|
||||
std::filesystem::remove(scratch, ec);
|
||||
std::filesystem::remove(*scratch, ec);
|
||||
// A competing launcher may have published its settings first. Always read
|
||||
// the winner from NAND rather than using our unpersisted candidate serial.
|
||||
const auto persisted = Read(root);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <charconv>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
|
||||
namespace RuntimeScSerial {
|
||||
|
||||
// SCGetProductSN's output is a u32, not a character buffer. DWC loads
|
||||
// that word and formats it with the product code to construct csnum.
|
||||
template <typename RangeValidator, typename WordWriter>
|
||||
uint32_t Write(std::string_view serial, uint32_t address,
|
||||
RangeValidator&& contains, WordWriter&& write32) {
|
||||
if (serial.empty() || serial.size() > 9 ||
|
||||
serial.find_first_not_of("0123456789") != std::string_view::npos) return 0;
|
||||
uint32_t number = 0;
|
||||
const auto parsed = std::from_chars(serial.data(), serial.data() + serial.size(), number);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != serial.data() + serial.size() ||
|
||||
!address || !contains(address, sizeof(uint32_t))) return 0;
|
||||
write32(address, number);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace RuntimeScSerial
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "hle_stubs.h"
|
||||
|
||||
#include "console_identity.h"
|
||||
#include "sc_serial_contract.h"
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -97,12 +98,9 @@ PPC_NATIVE_OVERRIDE(801B2424, SCGetProductCode_HLE, uint32_t, (), ());
|
||||
extern "C" uint32_t SCGetProductSN_HLE(uint32_t serialAddress)
|
||||
{
|
||||
const std::string& serial = RuntimeConsoleIdentity::Current().serial;
|
||||
if (!serialAddress || !Memory::Contains(serialAddress, serial.size() + 1)) {
|
||||
return 0;
|
||||
}
|
||||
std::memcpy(Memory::GetPointer(serialAddress, serial.size() + 1),
|
||||
serial.c_str(), serial.size() + 1);
|
||||
return 1;
|
||||
return RuntimeScSerial::Write(serial, serialAddress,
|
||||
[](uint32_t address, size_t size) { return Memory::Contains(address, size); },
|
||||
[](uint32_t address, uint32_t value) { Memory::Write32(address, value); });
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE(801B2460, SCGetProductSN_HLE, uint32_t, (uint32_t serialAddress), (serialAddress));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "nand_save_probe.h"
|
||||
#include "nand_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -7,6 +6,10 @@
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using RuntimeNandSave::ReadAction;
|
||||
using RuntimeNandSave::Contents;
|
||||
@@ -47,9 +50,11 @@ int main() {
|
||||
try {
|
||||
const auto save = root / "title/00010004/524d4350/data/rksys.dat";
|
||||
const auto shadow = fs::path(save.native() + fs::path(".nandsafe.tmp").native());
|
||||
std::string error;
|
||||
Require(RuntimeNandSettings::Ensure(root, error, 1800000123), "New profile settings bootstrap");
|
||||
const auto identity = Read(RuntimeNandSettings::FilePath(root));
|
||||
// Save inspection must leave unrelated NAND data alone. Settings
|
||||
// initialization is covered separately by nand_settings_tests.
|
||||
const auto settingsPath = root / "title/00000001/00000002/data/setting.txt";
|
||||
const std::string identity(256, '\x5a');
|
||||
Write(settingsPath, identity);
|
||||
Require(RuntimeNandSave::CheckRead(save, 1) == ReadAction::Proceed, "Fresh profile follows normal missing-file handling");
|
||||
Require(!fs::exists(save), "Probing fresh profile must not create a save");
|
||||
|
||||
@@ -126,8 +131,7 @@ int main() {
|
||||
SetFileAttributesW(save.c_str(), FILE_ATTRIBUTE_NORMAL);
|
||||
Require(readOnlyResult == ReadAction::Proceed && Read(save) == existing, "Readable read-only save remains available");
|
||||
#endif
|
||||
Require(RuntimeNandSettings::Ensure(root, error, 1900000123), "Existing profile settings bootstrap");
|
||||
Require(Read(RuntimeNandSettings::FilePath(root)) == identity, "Save recovery must not change console identity");
|
||||
Require(Read(settingsPath) == identity, "Save inspection must not change NAND settings");
|
||||
fs::remove_all(root);
|
||||
std::cout << "NAND save startup, preservation, interrupted-write and I/O failure scenarios passed\n";
|
||||
return 0;
|
||||
|
||||
@@ -43,6 +43,38 @@ int main() {
|
||||
Require(!RuntimeNandSettings::Read(root));
|
||||
Require(!std::filesystem::exists(root));
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
const auto scratchParent = root / "scratch-collisions";
|
||||
std::filesystem::create_directories(scratchParent / ".setting-init-fixed-0");
|
||||
const auto sentinel = scratchParent / ".setting-init-fixed-0" / "setting.txt";
|
||||
{ std::ofstream output(sentinel); output << "another launch owns this"; }
|
||||
const auto occupiedFile = scratchParent / ".setting-init-fixed-1";
|
||||
{ std::ofstream output(occupiedFile); output << "leave this file alone"; }
|
||||
std::error_code scratchError;
|
||||
const auto claimed = CreateScratchDirectory(scratchParent, "fixed", scratchError);
|
||||
Require(claimed && *claimed == scratchParent / ".setting-init-fixed-2" && !scratchError,
|
||||
"Retry collisions with both existing directories and files");
|
||||
Require(ReadBytes(sentinel) == "another launch owns this" &&
|
||||
ReadBytes(occupiedFile) == "leave this file alone", "Never modify another launch's scratch data");
|
||||
Require(!CreateScratchDirectory(occupiedFile / "not-a-directory", "fixed", scratchError) && scratchError,
|
||||
"Real filesystem errors must fail rather than retry indefinitely");
|
||||
|
||||
// Force all claimants to use the same token; this deterministically
|
||||
// exercises the collision path even when host clock precision is high.
|
||||
std::array<std::optional<std::filesystem::path>, 16> claims;
|
||||
std::vector<std::thread> claimants;
|
||||
for (size_t i = 0; i < claims.size(); ++i) {
|
||||
claimants.emplace_back([&, i] {
|
||||
std::error_code ec;
|
||||
claims[i] = CreateScratchDirectory(scratchParent, "shared", ec);
|
||||
});
|
||||
}
|
||||
for (auto& claimant : claimants) claimant.join();
|
||||
for (size_t i = 0; i < claims.size(); ++i) {
|
||||
Require(claims[i].has_value(), "Every concurrent claimant must acquire a scratch directory");
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
Require(claims[i] != claims[j], "Concurrent claimants must own different scratch directories");
|
||||
}
|
||||
}
|
||||
const std::string plain = "AREA=USA\r\n\nCODE=LU\r\nSERNO=987654321\r\nGAME=US\r\n";
|
||||
std::array<uint8_t, 256> fixture{};
|
||||
for (size_t i = 0; i < fixture.size(); ++i) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "sc_serial_contract.h"
|
||||
#include "nand_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
static void Require(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
static uint32_t ReadWord(const unsigned char* bytes) {
|
||||
return (uint32_t(bytes[0]) << 24) | (uint32_t(bytes[1]) << 16) |
|
||||
(uint32_t(bytes[2]) << 8) | uint32_t(bytes[3]);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
// Feed real generator + SC ABI outputs to the upstream bot decoder.
|
||||
// Usage: mkw_sc_serial_tests --timestamp-vectors <unix-seconds> ...
|
||||
if (argc > 1 && std::string(argv[1]) == "--timestamp-vectors") {
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const auto timestamp = std::stoll(argv[i]);
|
||||
const auto serial = RuntimeNandSettings::GenerateSerial(static_cast<std::time_t>(timestamp));
|
||||
std::array<unsigned char, 4> output{};
|
||||
Require(RuntimeScSerial::Write(serial, 4,
|
||||
[](uint32_t address, size_t size) { return address == 4 && size == 4; },
|
||||
[&](uint32_t, uint32_t value) {
|
||||
for (unsigned j = 0; j < 4; ++j)
|
||||
output[j] = static_cast<unsigned char>(value >> (24 - 8 * j));
|
||||
}) == 1, "Generated serial must pass SC ABI");
|
||||
std::cout << timestamp << '\t' << serial << "\tLEH"
|
||||
<< std::setfill('0') << std::setw(9) << ReadWord(output.data()) << '\n';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Reproduce the reported csnums from the old string-writing override.
|
||||
const unsigned char old7886[] = {'7', '8', '8', '6'};
|
||||
const unsigned char old7618[] = {'7', '6', '1', '8'};
|
||||
Require(ReadWord(old7886) == 926431286, "Reproduce shared LEH926431286");
|
||||
Require(ReadWord(old7618) == 926298424, "Reproduce shared LEH926298424");
|
||||
|
||||
std::array<unsigned char, 16> memory;
|
||||
size_t available = 4;
|
||||
unsigned writes = 0;
|
||||
const auto contains = [&](uint32_t address, size_t size) {
|
||||
return address == 4 && size <= available;
|
||||
};
|
||||
const auto write32 = [&](uint32_t address, uint32_t value) {
|
||||
++writes;
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
memory[address + i] = static_cast<unsigned char>(value >> (24 - 8 * i));
|
||||
};
|
||||
for (const auto& pair : {std::pair{"788600001", 788600001u}, {"788699999", 788699999u},
|
||||
{"761800001", 761800001u}, {"761899999", 761899999u},
|
||||
{"012345678", 12345678u}, {"000000001", 1u}, {"999999999", 999999999u}}) {
|
||||
memory.fill(0xa5);
|
||||
writes = 0;
|
||||
Require(RuntimeScSerial::Write(pair.first, 4, contains, write32) == 1, "Accept an exactly four-byte output buffer");
|
||||
Require(writes == 1 && ReadWord(memory.data() + 4) == pair.second, "Return full numeric serial, including digits after common prefix");
|
||||
for (size_t i = 0; i < memory.size(); ++i)
|
||||
if (i < 4 || i >= 8) Require(memory[i] == 0xa5, "Do not overwrite adjacent guest stack data");
|
||||
}
|
||||
for (const char* serial : {"", "1234567890", "7886x1234", "-12345678", "+12345678"}) {
|
||||
writes = 0;
|
||||
Require(RuntimeScSerial::Write(serial, 4, contains, write32) == 0 && writes == 0, "Reject malformed serial without a write");
|
||||
}
|
||||
writes = 0;
|
||||
Require(RuntimeScSerial::Write("788600001", 0, contains, write32) == 0 && writes == 0, "Reject null output");
|
||||
available = 3;
|
||||
Require(RuntimeScSerial::Write("788600001", 4, contains, write32) == 0 && writes == 0, "Reject undersized output");
|
||||
std::cout << "SC serial collision reproduction, numeric output and memory-boundary tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user