Initial Wii image generation (needs disc header)

This commit is contained in:
Jack Andersen
2016-01-21 16:30:17 -10:00
parent 3fab04ff1a
commit 7403996ed3
12 changed files with 789 additions and 32 deletions

View File

@@ -2,6 +2,7 @@
#include <string.h>
#include "NOD/DiscWii.hpp"
#include "NOD/aes.hpp"
#include "NOD/sha1.h"
namespace NOD
{
@@ -390,4 +391,368 @@ DiscWii::DiscWii(std::unique_ptr<IDiscIO>&& dio)
}
}
class PartitionBuilderWii : public DiscBuilderBase::IPartitionBuilder
{
uint64_t m_curUser = 0x40000;
public:
PartitionBuilderWii(DiscBuilderBase& parent, Kind kind,
const char gameID[6], const char* gameTitle)
: DiscBuilderBase::IPartitionBuilder(parent, kind, gameID, gameTitle) {}
uint64_t getCurUserEnd() const {return m_curUser;}
uint64_t userAllocate(uint64_t reqSz)
{
reqSz = ROUND_UP_32(reqSz);
if (m_curUser + reqSz >= 0x1FB450000)
{
LogModule.report(LogVisor::FatalError, "partition exceeds maximum single-partition capacity");
return -1;
}
uint64_t ret = m_curUser;
m_curUser += reqSz;
return ret;
}
bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn)
{
bool result = DiscBuilderBase::IPartitionBuilder::buildFromDirectory(dirIn, dolIn, apploaderIn);
if (!result)
return false;
std::unique_ptr<IFileIO::IWriteStream> ws;
/* Pad out user area to nearest cleartext sector */
uint64_t curUserRem = m_curUser % 0x1F0000;
if (curUserRem)
{
ws = m_parent.getFileIO().beginWriteStream(m_curUser);
curUserRem = 0x1F0000 - curUserRem;
for (size_t i=0 ; i<curUserRem ; ++i)
ws->write("\xff", 1);
m_curUser += curUserRem;
}
ws = m_parent.getFileIO().beginWriteStream(0);
Header header(m_gameID, m_gameTitle.c_str(), true);
header.write(*ws);
ws = m_parent.getFileIO().beginWriteStream(0x2440);
FILE* fp = Fopen(apploaderIn, _S("rb"), FileLockType::Read);
if (!fp)
LogModule.report(LogVisor::FatalError, _S("unable to open %s for reading"), apploaderIn);
char buf[8192];
size_t xferSz = 0;
SystemString apploaderName(apploaderIn);
++m_parent.m_progressIdx;
while (true)
{
size_t rdSz = fread(buf, 1, 8192, fp);
if (!rdSz)
break;
ws->write(buf, rdSz);
xferSz += rdSz;
if (0x2440 + xferSz >= 0x40000)
LogModule.report(LogVisor::FatalError,
"apploader flows into user area (one or the other is too big)");
m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz);
}
fclose(fp);
size_t fstOff = ROUND_UP_32(xferSz);
size_t fstSz = sizeof(FSTNode) * m_buildNodes.size();
for (size_t i=0 ; i<fstOff-xferSz ; ++i)
ws->write("\xff", 1);
fstOff += 0x2440;
ws->write(m_buildNodes.data(), fstSz);
for (const std::string& str : m_buildNames)
ws->write(str.data(), str.size()+1);
fstSz += m_buildNameOff;
fstSz = ROUND_UP_32(fstSz);
if (fstOff + fstSz >= 0x40000)
LogModule.report(LogVisor::FatalError,
"FST flows into user area (one or the other is too big)");
ws = m_parent.getFileIO().beginWriteStream(0x420);
uint32_t vals[4];
vals[0] = SBig(uint32_t(m_dolOffset >> 2));
vals[1] = SBig(uint32_t(fstOff >> 2));
vals[2] = SBig(uint32_t(fstSz));
vals[3] = SBig(uint32_t(fstSz));
ws->write(vals, sizeof(vals));
return true;
}
uint64_t cryptAndFakesign(IFileIO& out, uint64_t offset, const SystemChar* partHeadIn) const
{
/* Read head and validate key members */
FILE* fp = Fopen(partHeadIn, _S("rb"), FileLockType::Read);
if (!fp)
LogModule.report(LogVisor::FatalError, _S("unable to open %s for reading"), partHeadIn);
uint8_t tkey[16];
{
fseeko64(fp, 0x1BF, SEEK_SET);
if (fread(tkey, 1, 16, fp) != 16)
LogModule.report(LogVisor::FatalError, _S("unable to read title key from %s"), partHeadIn);
}
uint8_t tkeyiv[16] = {};
{
fseeko64(fp, 0x1DC, SEEK_SET);
if (fread(tkeyiv, 1, 8, fp) != 8)
LogModule.report(LogVisor::FatalError, _S("unable to read title key IV from %s"), partHeadIn);
}
uint8_t ccIdx;
{
fseeko64(fp, 0x1F1, SEEK_SET);
if (fread(&ccIdx, 1, 1, fp) != 1)
LogModule.report(LogVisor::FatalError, _S("unable to read common key index from %s"), partHeadIn);
if (ccIdx > 1)
LogModule.report(LogVisor::FatalError, _S("common key index may only be 0 or 1"));
}
uint32_t tmdSz;
{
fseeko64(fp, 0x2A4, SEEK_SET);
if (fread(&tmdSz, 1, 4, fp) != 4)
LogModule.report(LogVisor::FatalError, _S("unable to read TMD size from %s"), partHeadIn);
tmdSz = SBig(tmdSz);
}
uint64_t h3Off;
{
uint32_t h3Ptr;
fseeko64(fp, 0x2B4, SEEK_SET);
if (fread(&h3Ptr, 1, 4, fp) != 4)
LogModule.report(LogVisor::FatalError, _S("unable to read H3 pointer from %s"), partHeadIn);
h3Off = uint64_t(SBig(h3Ptr)) << 2;
}
uint64_t dataOff;
{
uint32_t dataPtr;
if (fread(&dataPtr, 1, 4, fp) != 4)
LogModule.report(LogVisor::FatalError, _S("unable to read data pointer from %s"), partHeadIn);
dataOff = uint64_t(SBig(dataPtr)) << 2;
}
std::unique_ptr<uint8_t[]> tmdData(new uint8_t[tmdSz]);
fseeko64(fp, 0x2C0, SEEK_SET);
if (fread(tmdData.get(), 1, tmdSz, fp) != tmdSz)
LogModule.report(LogVisor::FatalError, _S("unable to read TMD from %s"), partHeadIn);
/* Copy partition head up to H3 table */
std::unique_ptr<IFileIO::IWriteStream> ws = out.beginWriteStream(offset);
{
uint64_t remCopy = h3Off;
uint8_t copyBuf[8192];
fseeko64(fp, 0, SEEK_SET);
while (remCopy)
{
size_t rdBytes = fread(copyBuf, 1, std::min(8192ul, remCopy), fp);
if (rdBytes)
{
ws->write(copyBuf, rdBytes);
remCopy -= rdBytes;
continue;
}
for (size_t i=0 ; i<remCopy ; ++i)
ws->write("", 1);
break;
}
}
fclose(fp);
/* Prepare crypto pass */
std::unique_ptr<IFileIO::IReadStream> rs = m_parent.getFileIO().beginReadStream(0);
sha1nfo sha;
std::unique_ptr<IAES> aes = NewAES();
aes->setKey(COMMON_KEYS[ccIdx]);
aes->decrypt(tkeyiv, tkey, tkey, 16);
aes->setKey(tkey);
static const uint8_t ZEROIV[16] = {0};
std::unique_ptr<char[]> cleartext(new char[0x1F0000]);
std::unique_ptr<char[]> ciphertext(new char[0x200000]);
uint8_t h3[4916][20] = {};
uint64_t groupCount = m_curUser / 0x1F0000;
ws = out.beginWriteStream(offset + dataOff);
SystemString cryptoName(_S("Hashing and encrypting"));
++m_parent.m_progressIdx;
for (uint64_t g=0 ; g<groupCount ; ++g)
{
char* cleartext2 = cleartext.get();
char* ciphertext2 = ciphertext.get();
if (rs->read(cleartext2, 0x1F0000) != 0x1F0000)
LogModule.report(LogVisor::FatalError, "cleartext file too short");
uint8_t h2[8][20];
for (int s=0 ; s<8 ; ++s)
{
char* cleartext1 = cleartext2 + s*0x3E000;
char* ciphertext1 = ciphertext2 + s*0x40000;
uint8_t h1[8][20];
for (int c=0 ; c<8 ; ++c)
{
char* cleartext0 = cleartext1 + c*0x7c00;
char* ciphertext0 = ciphertext1 + c*0x8000;
uint8_t h0[31][20];
for (int j=0 ; j<31 ; ++j)
{
sha1_init(&sha);
sha1_write(&sha, cleartext0 + j*0x400, 0x400);
memcpy(h0[j], sha1_result(&sha), 20);
}
sha1_init(&sha);
sha1_write(&sha, (char*)h0, 0x26C);
memcpy(h1[c], sha1_result(&sha), 20);
memcpy(ciphertext0, h0, 0x26C);
memset(ciphertext0+0x26C, 0, 0x014);
}
sha1_init(&sha);
sha1_write(&sha, (char*)h1, 0x0A0);
memcpy(h2[s], sha1_result(&sha), 20);
for (int c=0 ; c<8 ; ++c)
{
char* ciphertext0 = ciphertext1 + c*0x8000;
memcpy(ciphertext0+0x280, h1, 0x0A0);
memset(ciphertext0+0x320, 0, 0x020);
}
}
sha1_init(&sha);
sha1_write(&sha, (char*)h2, 0x0A0);
memcpy(h3[g], sha1_result(&sha), 20);
for (int s=0 ; s<8 ; ++s)
{
char* cleartext1 = cleartext2 + s*0x3E000;
char* ciphertext1 = ciphertext2 + s*0x40000;
for (int c=0 ; c<8 ; ++c)
{
char* cleartext0 = cleartext1 + c*0x7c00;
char* ciphertext0 = ciphertext1 + c*0x8000;
memcpy(ciphertext0+0x340, h2, 0x0A0);
memset(ciphertext0+0x3E0, 0, 0x020);
aes->encrypt(ZEROIV, (uint8_t*)ciphertext0, (uint8_t*)ciphertext0, 0x400);
aes->encrypt((uint8_t*)(ciphertext0+0x3D0), (uint8_t*)cleartext0, (uint8_t*)(ciphertext0+0x400), 0x7c00);
}
}
if (ws->write(ciphertext2, 0x200000) != 0x200000)
LogModule.report(LogVisor::FatalError, "unable to write full disc sector");
m_parent.m_progressCB(m_parent.m_progressIdx, cryptoName, (g+1)*0x200000);
}
/* Write new crypto content size */
uint64_t cryptContentSize = (groupCount * 0x200000) >> 2;
uint32_t cryptContentSizeBig = SBig(uint32_t(cryptContentSize));
ws = out.beginWriteStream(offset + 0x2BC);
ws->write(&cryptContentSizeBig, 0x4);
/* Write new H3 */
ws = out.beginWriteStream(offset + h3Off);
ws->write(h3, 0x18000);
/* Compute content hash and replace in TMD */
sha1_init(&sha);
sha1_write(&sha, (char*)h3, 0x18000);
memcpy(tmdData.get() + 0x1F4, sha1_result(&sha), 20);
/* Same for content size */
uint64_t contentSize = groupCount * 0x1F0000;
uint64_t contentSizeBig = SBig(contentSize);
memcpy(tmdData.get() + 0x1EC, &contentSizeBig, 8);
/* Zero-out TMD signature to simplify brute-force */
memset(tmdData.get() + 0x4, 0, 0x100);
/* Brute-force zero-starting hash */
size_t tmdCheckSz = tmdSz - 0x140;
struct BFWindow
{
uint64_t word[7];
}* bfWindow = (BFWindow*)(tmdData.get() + 0x19A);
bool good = false;
uint64_t attempts = 0;
SystemString bfName(_S("Brute force attempts"));
++m_parent.m_progressIdx;
for (int w=0 ; w<7 ; ++w)
{
for (uint64_t i=0 ; i<UINT64_MAX ; ++i)
{
bfWindow->word[w] = i;
sha1_init(&sha);
sha1_write(&sha, (char*)(tmdData.get() + 0x140), tmdCheckSz);
uint8_t* hash = sha1_result(&sha);
if (hash[0] == 0)
{
good = true;
break;
}
++attempts;
if ((attempts % 1024) == 0)
m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts);
}
if (good)
break;
}
m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts);
ws = out.beginWriteStream(offset + 0x2C0);
ws->write(tmdData.get(), tmdSz);
return offset + dataOff + groupCount * 0x200000;
}
};
bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn,
const SystemChar* apploaderIn, const SystemChar* partHeadIn)
{
PartitionBuilderWii& pb = static_cast<PartitionBuilderWii&>(*m_partitions[0]);
uint64_t filledSz = 0x200000;
std::unique_ptr<IFileIO> imgOut = NewFileIO(m_outPath);
m_fileIO = std::move(NewFileIO(SystemString(m_outPath) + _S(".cleardata")));
if (!pb.buildFromDirectory(dirIn, dolIn, apploaderIn))
return false;
filledSz = pb.cryptAndFakesign(*imgOut, filledSz, partHeadIn);
if (filledSz >= 0x1FB4E0000)
{
LogModule.report(LogVisor::FatalError, "data partition exceeds disc capacity");
return false;
}
/* Fill image to end */
std::unique_ptr<IFileIO::IWriteStream> ws = imgOut->beginWriteStream(filledSz);
for (size_t i=0 ; i<0x1FB4E0000-filledSz ; ++i)
ws->write("\xff", 1);
return true;
}
DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle,
std::function<void(size_t, const SystemString&, size_t)> progressCB)
: DiscBuilderBase(std::move(std::unique_ptr<IFileIO>()), progressCB), m_outPath(outPath)
{
PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, IPartitionBuilder::Kind::Data,
gameID, gameTitle);
m_partitions.emplace_back(partBuilder);
}
}