/* * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved. */ #include #include #include #include #include #include #include #include #include "pack.hpp" namespace nostalgia { using Preloader = ox::ModelHandlerInterface; static ox::Error pathToInode(ox::FileSystem *dest, ox::ModelObject *obj) noexcept { auto &o = *obj; auto type = static_cast(o["type"].get()); auto &data = o["data"].get(); ox::String path; switch (type) { case ox::FileAddressType::Path: path = data["path"].get(); break; case ox::FileAddressType::ConstPath: path = data["constPath"].get(); break; case ox::FileAddressType::Inode: case ox::FileAddressType::None: return {}; } oxRequire(s, dest->stat(path.c_str())); oxReturnError(o["type"].set(static_cast(ox::FileAddressType::Inode))); return data.set(2, s.inode); } /** * Convert path references in Claw data to inodes to save space * @param buff buffer holding file * @return error */ static ox::Error transformObj(ox::FileSystem *dest, ox::ModelObject *obj) noexcept { for (auto &f : *obj) { auto &v = f->value; if (v.type() != ox::ModelValue::Type::Object) { continue; } auto &o = v.get(); if (o.typeName() == "net.drinkingtea.ox.FileAddress" && o.typeVersion() == 1) { oxReturnError(pathToInode(dest, &o)); } else { oxReturnError(transformObj(dest, &o)); } } return {}; } static ox::Error doTransformations(core::TypeStore *ts, ox::FileSystem *dest, ox::CRString filePath) noexcept { if (filePath.endsWith(".ng") || filePath.endsWith(".npal")) { // load file oxRequire(s, dest->stat(filePath.c_str())); oxRequireM(buff, dest->read(s.inode)); if (filePath.endsWith(".ng")) { oxReturnError(core::convertBuffToBuff(buff, ox::ClawFormat::Metal).moveTo(&buff)); } oxRequireM(obj, ox::readClaw(ts, buff)); // do transformations oxReturnError(transformObj(dest, &obj)); oxReturnError(ox::writeClaw(&obj).moveTo(&buff)); // write file to dest oxReturnError(dest->write(s.inode, buff.data(), buff.size())); } return {}; } // claw file transformations are broken out because path to inode // transformations need to be done after the copy to the new FS is complete static ox::Error transformClaw(core::TypeStore *ts, ox::FileSystem *dest, ox::CRString path) noexcept { // copy oxTracef("pack::transformClaw", "path: {}", path); oxRequire(fileList, dest->ls(path)); for (const auto &name : fileList) { const auto filePath = path + name; oxRequire(stat, dest->stat(filePath.c_str())); if (stat.fileType == ox::FileType::Directory) { const auto dir = path + name + '/'; oxReturnError(transformClaw(ts, dest, dir)); } else { oxReturnError(doTransformations(ts, dest, filePath)); } } return {}; } static ox::Error verifyFile(ox::FileSystem *fs, ox::CRString path, const ox::Buffer &expected) noexcept { ox::Buffer buff(expected.size()); oxReturnError(fs->read(path.c_str(), buff.data(), buff.size())); return OxError(buff == expected ? 0 : 1); } struct VerificationPair { ox::String path; ox::Buffer buff; VerificationPair(ox::String &&pPath, ox::Buffer &&pBuff) noexcept: path(std::forward(pPath)), buff(std::forward(pBuff)) { } }; static ox::Error copy(ox::FileSystem *src, ox::FileSystem *dest, ox::CRString path) noexcept { oxOutf("copying directory: {}\n", path); ox::Vector verificationPairs; // copy oxRequire(fileList, src->ls(path)); for (const auto &name : fileList) { auto currentFile = ox::sfmt("{}{}", path, name); if (currentFile == "/.nostalgia") { continue; } oxOutf("reading {}\n", currentFile); oxRequire(stat, src->stat(currentFile.c_str())); if (stat.fileType == ox::FileType::Directory) { oxReturnError(dest->mkdir(currentFile.c_str(), true)); oxReturnError(copy(src, dest, currentFile + '/')); } else { // load file oxRequireM(buff, src->read(currentFile.c_str())); // write file to dest oxOutf("writing {}\n", currentFile); oxReturnError(dest->write(currentFile.c_str(), buff.data(), buff.size())); oxReturnError(verifyFile(dest, currentFile, buff)); verificationPairs.emplace_back(std::move(currentFile), std::move(buff)); } } // verify all at once in addition to right after the files are written for (const auto &v : verificationPairs) { oxReturnError(verifyFile(dest, v.path, v.buff)); } return {}; } // transformations need to be done after the copy to the new FS is complete static ox::Error preloadObj(core::TypeStore *ts, ox::FileSystem *romFs, Preloader *pl, ox::CRString path) noexcept { // load file oxRequireM(buff, romFs->read(path.c_str())); oxRequireM(obj, ox::readClaw(ts, buff)); if (obj.type()->preloadable) { // preload oxReturnError(model(pl, &obj)); const core::PreloadPtr p{.preloadAddr = 0}; oxReturnError(ox::writeMC(&p).moveTo(&buff)); } else { // strip the Claw header (it is not needed after preloading) and write back out to dest fs oxReturnError(ox::writeMC(&obj).moveTo(&buff)); } oxReturnError(romFs->write(path.c_str(), buff.data(), buff.size())); return {}; } // claw file transformations are broken out because path to inode // transformations need to be done after the copy to the new FS is complete static ox::Error preload(core::TypeStore *ts, ox::FileSystem *romFs, Preloader *pl, ox::CRString path) noexcept { // copy oxTracef("pack::preload", "path: {}", path); oxRequire(fileList, romFs->ls(path)); for (const auto &name : fileList) { const auto filePath = path + name; oxRequire(stat, romFs->stat(filePath.c_str())); if (stat.fileType == ox::FileType::Directory) { const auto dir = path + name + '/'; oxReturnError(preload(ts, romFs, pl, dir)); } else { oxReturnError(preloadObj(ts, romFs, pl, filePath)); } } return {}; } static ox::Error padbin(ox::BufferWriter *w, unsigned factor) noexcept { return w->write(nullptr, factor - w->buff().size() % factor); } ox::Error appendBinary(ox::Buffer *binBuff, ox::Buffer *fsBuff, GbaPreloader *pl) noexcept { constexpr ox::StringView mediaHdr = "NOSTALGIA_MEDIA_HEADER__________"; constexpr ox::StringView preloadHdr = "NOSTALGIA_PRELOAD_HEADER________"; constexpr auto hdrSize = 32; static_assert(mediaHdr.bytes() == hdrSize); static_assert(preloadHdr.bytes() == hdrSize); ox::BufferWriter w(binBuff); oxReturnError(padbin(&w, hdrSize)); oxReturnError(w.write(mediaHdr.data(), mediaHdr.bytes())); oxReturnError(w.write(fsBuff->data(), fsBuff->size())); oxReturnError(padbin(&w, hdrSize)); oxReturnError(w.write(preloadHdr.data(), preloadHdr.bytes())); const auto &plBuff = pl->buff(); oxReturnError(pl->offsetPtrs(binBuff->size())); oxReturnError(w.write(plBuff.data(), plBuff.size())); return {}; } ox::Error pack(core::TypeStore *ts, ox::FileSystem *src, ox::FileSystem *dest) noexcept { oxReturnError(copy(src, dest, "/")); oxReturnError(ox::buildTypeDef(ts)); oxReturnError(transformClaw(ts, dest, "/")); return {}; } ox::Error preload(core::TypeStore *ts, ox::FileSystem *src, GbaPreloader *pl) noexcept { return preload(ts, src, pl->interface(), "/"); } }