diff --git a/Makefile.common b/Makefile.common index af5e53d7..f0a92385 100644 --- a/Makefile.common +++ b/Makefile.common @@ -355,6 +355,7 @@ tools_OBJS := \ engines/touche/compress_touche.o \ engines/tucker/compress_tucker.o \ engines/agos/extract_agos.o \ + engines/agos/extract_simon_acorn.o \ engines/asylum/extract_asylum.o \ engines/cge/extract_cge.o \ engines/cge/pack_cge.o \ diff --git a/engines/agos/extract_simon_acorn.cpp b/engines/agos/extract_simon_acorn.cpp new file mode 100644 index 00000000..81042a8a --- /dev/null +++ b/engines/agos/extract_simon_acorn.cpp @@ -0,0 +1,1593 @@ +/* ScummVM Tools + * + * ScummVM Tools is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* The ADFS filesystem parsing routines are based on Gerald Holdsworth's + * DiscImageManager: + * https://github.com/geraldholdsworth/DiscImageManager/ + * + * Many thanks to Mike Woodroffe of Adventure Soft for giving + * permission for this tool to include the disk 10 data. + */ + +#include "engines/agos/extract_simon_acorn.h" + +#include "common/endian.h" +#include "common/file.h" +#include "common/ptr.h" +#include "common/algorithm.h" +#include "common/util.h" + +#include +#ifdef WIN32 +#include +#else +#include +#include +#endif + +static bool pathExists(const Common::String &p) { + return Common::Filename(p).exists(); +} + + +static void makeDir(Tool *tool, const Common::String &path) { + if (path.empty() || pathExists(path)) { + return; + } + +#ifdef WIN32 + int result = _mkdir(path.c_str()); +#else + int result = mkdir(path.c_str(), 0755); +#endif + + if (result != 0 && errno != EEXIST && !pathExists(path) && tool != nullptr) { + tool->warning("Could not create directory: %s", path.c_str()); + } +} + +static void ensureDir(Tool *tool, const Common::String &p) { + if (p.empty()) + return; + + Common::String cur; + for (uint i = 0; i < p.size(); i++) { + char c = p[i]; + cur += c; + if ((c == '/' || c == '\\') && !cur.empty()) + makeDir(tool, cur); + } + + makeDir(tool, p); +} + + +static Common::String joinPath(const Common::String &a, const Common::String &b) { + if (a.empty()) return b; + if (b.empty()) return a; + if (a[a.size() - 1] == '/' || a[a.size() - 1] == '\\') return a + b; + return a + "/" + b; +} + +static Common::String dirnameOf(const Common::String &p) { + int pos = -1; + for (int i = (int)p.size() - 1; i >= 0; i--) { + if (p[i] == '/' || p[i] == '\\') { pos = i; break; } + } + if (pos < 0) return ""; + return Common::String(p.c_str(), p.c_str() + pos); +} + +class AdfsVolume; +struct AdfsObject; + +static bool looksLikeAdfsDirBlock(const Common::Array &buf); +static bool readFileAnyDisk(const Common::HashMap &vols, const Common::String &adfsFilePath, uint32 wantLen, Common::Array &outData); + +static uint32 rd32le(const byte *p) { + return READ_LE_UINT32(p); +} + +static uint32 rd24le(const byte *p) { + return (uint32)p[0] | ((uint32)p[1] << 8) | ((uint32)p[2] << 16); +} + +static Common::String toLower(Common::String s) { + s.toLowercase(); + return s; +} + +static bool ieq(const Common::String &a, const Common::String &b) { + return toLower(a) == toLower(b); +} + +static Common::String trimSpaces(const Common::String &s) { + uint b = 0; + while (b < s.size() && (s[b] == ' ' || s[b] == '\t' || s[b] == '\r' || s[b] == '\n')) b++; + uint e = s.size(); + while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\r' || s[e - 1] == '\n')) e--; + return Common::String(s.c_str() + b, s.c_str() + e); +} + +static bool matchStarPatternICase(const Common::String &name, const Common::String &pattern) { + Common::String n = toLower(name); + Common::String p = toLower(pattern); + if (p == "*") { + return true; + } + + int starPos = -1; + for (int i = 0; i < (int)p.size(); i++) { + if (p[i] == '*') { starPos = i; break; } + } + if (starPos < 0) { + return n == p; + } + + Common::String pre(p.c_str(), p.c_str() + starPos); + Common::String post(p.c_str() + starPos + 1, p.c_str() + p.size()); + + if (!pre.empty()) { + if (n.size() < pre.size()) return false; + if (Common::String(n.c_str(), n.c_str() + pre.size()) != pre) return false; + } + if (!post.empty()) { + if (n.size() < post.size()) return false; + if (Common::String(n.c_str() + n.size() - post.size(), n.c_str() + n.size()) != post) return false; + } + return true; +} + +static Common::Array readFileBytes(const Common::String &p) { + Common::File f; + f.open(Common::Filename(p), "rb"); + if (!f.isOpen()) + error("Failed to open file: %s", p.c_str()); + uint32 sz = f.size(); + Common::Array out; + if (sz > 0) { + out.resize(sz); + if (f.read_noThrow(out.begin(), sz) != sz) + error("Failed to read file: %s", p.c_str()); + } + f.close(); + return out; +} + +static void writeFileBytes(Tool *tool, const Common::String &p, const Common::Array &data) { + ensureDir(tool, dirnameOf(p)); + Common::File f; + f.open(Common::Filename(p), "wb"); + if (!f.isOpen()) + error("Failed to write file: %s", p.c_str()); + if (!data.empty() && f.write(data.begin(), data.size()) != data.size()) + error("Failed to write file: %s", p.c_str()); + f.close(); +} + +static bool isAllZeros(const Common::Array &v) { + for (uint i = 0; i < v.size(); i++) if (v[i] != 0) return false; + return true; +} + +// Write data to path only if it would be an improvement over what's already there. +static bool writeFileBytesIfBetter(Tool *tool, const Common::String &p, const Common::Array &data) { + if (data.empty() || isAllZeros(data)) { + return false; + } + if (pathExists(p)) { + Common::Array existing = readFileBytes(p); + if (existing.size() == data.size() && memcmp(existing.begin(), data.begin(), data.size()) == 0) { + return false; + } + } + writeFileBytes(tool, p, data); + return true; +} + +static void logSuccess(const Common::String &relPath, const char *kind, int disk) { + debug(1, "Success: %s (%s, disk %d)", relPath.c_str(), kind, disk); +} +static void logSuccess(const Common::String &relPath, const char *kind, const char *label) { + debug(1, "Success: %s (%s, %s)", relPath.c_str(), kind, label); +} + +//0621 patch data from disk 10 +static const byte kFIX_0621[] = { + 0x9E, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x02, 0x06, 0x01, 0x02, 0x0A, 0x04, 0x02, 0x0E, 0x1A, 0x02, 0x12, 0x22, 0x02, 0x16, 0x3A, 0x44, + 0x04, 0x18, 0x38, 0x04, 0x1C, 0x3A, 0x18, 0x3C, 0x02, 0x24, 0x3C, 0x18, 0x3B, 0x02, 0x23, 0x6A, + 0x18, 0x3A, 0x00, 0x00, 0x24, 0x28, 0x02, 0xFA, 0x03, 0x1A, 0x04, 0x86, 0x02, 0x3A, 0x17, 0x00, + 0x29, 0x00, 0x03, 0x02, 0x36, 0x65, 0x04, 0x46, 0x00, 0x00, 0x80, 0x31, 0x2D, 0x00, 0x0F, 0x00, + 0x2E, 0x00, 0x10, 0x03, 0x14, 0x06, 0x3E, 0x02, 0x00, 0x18, 0x02, 0x04, 0x05, 0x1F, 0x0C, 0x00, + 0xBA, 0xA9, 0x05, 0x02, 0x0E, 0x03, 0x0C, 0x0E, 0x03, 0x72, 0x09, 0x1C, 0x05, 0x0C, 0x2A, 0x02, + 0x96, 0xFF, 0xFE, 0x08, 0x38, 0x07, 0x02, 0xAA, 0x0A, 0x08, 0x46, 0xAA, 0xE2, 0x11, 0x0C, 0x54, + 0x10, 0x0C, 0x62, 0x0F, 0x0C, 0x70, 0x0E, 0x0C, 0x7E, 0x0D, 0x06, 0xEA, 0x0F, 0x00, 0x62, 0x0A, + 0x58, 0x03, 0xFC, 0x09, 0x74, 0xBE, 0xA6, 0xF6, 0x0C, 0x90, 0x03, 0xBE, 0x0F, 0xAC, 0x0F, 0xC8, + 0x0D, 0xE4, 0x0F, 0x12, 0x32, 0x3C, 0x12, 0x3E, 0x12, 0x1A, 0x04, 0x66, 0x16, 0x06, 0x19, 0x1F, + 0x2E, 0xFF, 0xAF, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, + 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x1F, 0x2E, 0x16, 0xBE, 0x0C, 0x1C, 0x2E, 0x0B, 0x1C, + 0xDA, 0x5F, 0xFD, 0x03, 0x1C, 0x19, 0xE8, 0x0D, 0x1C, 0x13, 0xBA, 0x29, 0x04, 0x09, 0x2C, 0x12, + 0x08, 0x12, 0xE8, 0xFF, 0x1B, 0xE8, 0x0A, 0x0E, 0x1F, 0x90, 0x1F, 0x90, 0x2F, 0x58, 0x1F, 0x90, + 0xFD, 0xFF, 0x15, 0x90, 0x3B, 0x1F, 0x90, 0x1F, 0x90, 0x2F, 0xBE, 0x2F, 0xBE, 0x2F, 0xBE, 0x2F, + 0xBE, 0x2F, 0xBE, 0x2F, 0xBE, 0x2E, 0xBE, 0x02, 0xF2, 0x2A, 0x74, 0x13, 0x0E, 0x29, 0x82, 0x1D, + 0x2A, 0xFF, 0xFF, 0x1F, 0x62, 0x1F, 0x62, 0x1F, 0x7E, 0x2A, 0xE8, 0x1A, 0x54, 0x32, 0x04, 0x1A, + 0x62, 0x3D, 0x20, 0x3D, 0x3C, 0x3D, 0x58, 0x33, 0x74, 0x3D, 0x90, 0x3F, 0x1C, 0x3F, 0xC8, 0x3F, + 0xE4, 0x3F, 0x1C, 0xAE, 0x01, 0x0F, 0x42, 0x42, 0x3F, 0x1C, 0x35, 0x1C, 0x28, 0x48, 0x3E, 0x18, + 0x44, 0x8C, 0x24, 0x32, +}; +static const uint kFIX_0621_LEN = 324u; + +//0622 patch data from disk 10 +static const byte kFIX_0622[] = { + 0x98, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, + 0x06, 0x90, 0x80, 0x22, 0x00, 0x10, 0x00, 0x00, 0x01, 0x10, 0x80, 0x20, 0x54, 0x1A, 0x00, 0x20, + 0x02, 0x08, 0x7C, 0x06, 0x08, 0xEA, 0x05, 0x10, 0x02, 0x5C, 0x06, 0x08, 0xCE, 0x02, 0x28, 0x02, + 0x20, 0x03, 0x48, 0x80, 0x22, 0x14, 0x18, 0x04, 0x08, 0xA8, 0x80, 0x17, 0x03, 0x30, 0x04, 0x12, + 0x80, 0x1B, 0x04, 0x08, 0x80, 0x06, 0x08, 0xEE, 0x80, 0x1A, 0xA1, 0x50, 0x03, 0x48, 0x05, 0x50, + 0x80, 0x19, 0x04, 0x08, 0xB2, 0x05, 0x20, 0x06, 0x18, 0x80, 0x1D, 0x04, 0x08, 0x74, 0x06, 0x08, + 0xD8, 0x09, 0x08, 0x05, 0x10, 0x07, 0x3C, 0x03, 0x50, 0x1D, 0x00, 0xFD, 0x0D, 0x0D, 0x03, 0x08, + 0x02, 0x06, 0x0C, 0x0C, 0x0B, 0x00, 0x00, 0x00, 0xFE, 0x0D, 0x0D, 0x07, 0xDD, 0xFE, 0x23, 0x00, + 0x02, 0x05, 0xF8, 0x04, 0x00, 0xD3, 0xDC, 0xCC, 0x40, 0x00, 0xCD, 0xDE, 0xDE, 0x04, 0xED, 0x0F, + 0x02, 0x12, 0x44, 0x02, 0x54, 0xFC, 0x55, 0x43, 0xCC, 0x2A, 0x03, 0x00, 0x00, 0xCC, 0xFF, 0xDC, + 0x03, 0xDD, 0xFC, 0xDC, 0xCC, 0xCC, 0xDC, 0x0B, 0xDD, 0xFF, 0x23, 0x04, 0x55, 0x40, 0x10, 0xFD, + 0x23, 0xCC, 0x2A, 0x04, 0xCC, 0x02, 0x13, 0xCD, 0xCC, 0xBC, 0xCC, 0x0B, 0x02, 0x3C, 0x50, 0x02, + 0x55, 0x00, 0x40, 0xF8, 0x54, 0x40, 0xC3, 0x2C, 0xCC, 0xCD, 0xCE, 0xDE, 0x06, 0xDD, 0xFF, 0xCD, + 0x0C, 0x02, 0x27, 0x05, 0x00, 0x20, 0x00, 0xFA, 0xD0, 0xCD, 0xCC, 0xCC, 0xD0, 0xD0, 0x04, 0xE0, + 0x05, 0xDE, 0x09, 0x02, 0x38, 0x18, 0x00, 0x00, 0x00, 0x04, 0xD0, 0xFC, 0xDD, 0xDD, 0xD3, 0x20, + 0x00, 0x15, 0x00, 0x07, 0x0D, 0xFF, 0x02, 0x11, 0x00, 0x00, 0x00, 0xFB, 0x0D, 0x00, 0xD0, 0xD0, + 0xDC, 0x04, 0xDD, 0x03, 0xDE, 0xFF, 0x34, 0x06, 0x00, 0xFA, 0x0D, 0x00, 0x00, 0x0C, 0x0D, 0x02, + 0x3E, 0x20, 0x14, 0x00, 0xF5, 0x0D, 0x0B, 0xDC, 0xC2, 0xDB, 0xDE, 0xEB, 0xB2, 0x00, 0x00, 0xCB, + 0x21, 0xEE, 0x12, 0x00, 0xF3, 0xD0, 0xB0, 0xCD, 0xB2, 0xCB, 0xBD, 0xDB, 0xB2, 0xEB, 0xBD, 0x00, + 0x00, 0xEE, 0xA1, 0xEE, 0x16, 0x00, 0xF7, 0xD0, 0xB0, 0xC0, 0xBD, 0xEC, 0xED, 0xC2, 0x2E, 0xE0, + 0x1D, 0x00, 0x40, 0x00, 0xFE, 0x30, 0x20, 0x06, 0x00, 0xFC, 0x0C, 0x0C, 0x0D, 0x0D, 0x02, 0x0E, + 0x17, 0x02, 0x70, 0xC0, 0x00, 0x00, 0xD0, 0xDD, 0xDD, 0xDE, 0x02, 0xEE, 0xFF, 0x44, 0x1B, 0x00, + 0xFD, 0x30, 0x20, 0x30, 0x7F, 0x00, 0xBE, 0x82, 0x64, 0x0F, 0x6C, 0x0F, 0x6C, 0x0F, 0x6C, 0x0F, + 0x6C, 0x07, 0x6C, 0x06, 0x13, 0x27, 0x14, 0x09, 0x71, 0x0E, 0x1A, 0x00, 0xFA, 0xD3, 0x02, 0x6D, + 0x00, 0xC0, 0xEE, 0x0E, 0x19, 0x00, 0xFA, 0x20, 0xD2, 0xD3, 0xE3, 0xE4, 0x40, 0x7F, 0x00, 0x67, + 0x0F, 0xDA, 0x0F, 0xDA, 0x07, 0xB0, 0x0F, 0xDA, 0x0F, 0xDA, 0x0F, 0x6E, 0x30, 0x20, 0x02, 0x00, + 0xFF, 0x0E, 0x02, 0x00, 0xFD, 0x02, 0x71, 0x02, 0x68, 0xE3, 0x02, 0xDE, 0x80, 0xBE, 0xDE, 0xEE, + 0x18, 0x00, 0xF9, 0x32, 0x24, 0x06, 0x72, 0x69, 0x1F, 0x4C, 0x1F, 0x4C, 0x1F, 0x4C, 0x1F, 0x4C, + 0x17, 0x4C, 0x05, 0x03, 0x64, 0x00, 0x00, 0xCD, 0x13, 0x00, 0xF5, 0x10, 0x2A, 0x32, 0x0C, 0x03, + 0x0C, 0x0C, 0xD0, 0x0D, 0xDD, 0x0E, 0x16, 0x40, 0x40, 0x00, 0xF8, 0x10, 0xCC, 0x22, 0xCC, 0x22, + 0x11, 0xE0, 0x19, 0x00, 0xFC, 0xC0, 0x20, 0x30, 0x02, 0xE3, 0x6E, 0xA8, 0x28, 0x00, 0x00, 0x17, + 0x14, 0xBE, 0x13, 0x1E, 0xBE, 0xF8, 0x13, 0xBE, 0x0E, 0x05, 0x30, 0x12, 0xC0, 0xF3, 0x1A, 0xC0, + 0x05, 0x05, 0x25, 0x02, 0x1F, 0xC2, 0x18, 0x19, 0xC2, 0xE5, 0x07, 0x06, 0x76, 0xF2, 0x10, 0x1A, + 0x03, 0x76, 0xDC, 0xDD, 0xCD, 0x00, 0xDD, 0xDE, 0x20, 0x09, 0x00, 0xE0, 0x15, 0x00, 0xF7, 0x02, + 0x79, 0xBB, 0xBC, 0x22, 0x8A, 0xE0, 0x1A, 0x07, 0x7A, 0x7E, 0x00, 0x00, 0x0D, 0x85, 0x00, 0x24, + 0x38, 0x09, 0x2A, 0x38, 0xFA, 0x34, 0x00, 0x04, 0x02, 0x74, 0x12, 0x00, 0xFC, 0x43, 0x34, 0x00, + 0x05, 0x13, 0x00, 0x28, 0x00, 0xFE, 0x43, 0x54, 0x15, 0x00, 0xFD, 0x40, 0x54, 0x05, 0x08, 0x23, + 0xD7, 0x0C, 0x1D, 0x42, 0x0E, 0x00, 0x10, 0x08, 0xF7, 0x0D, 0xDC, 0xCC, 0x15, 0x45, 0x0D, 0x00, + 0xF6, 0xD0, 0x3D, 0xCC, 0x16, 0x48, 0x0F, 0x00, 0x02, 0xD0, 0xA0, 0x02, 0x7F, 0x00, 0x1E, 0x00, + 0x0C, 0x24, 0x98, 0x08, 0x2A, 0x98, 0xF8, 0x02, 0x60, 0x00, 0x30, 0x20, 0x05, 0x04, 0x0F, 0x00, + 0x04, 0x00, 0xF9, 0x43, 0x30, 0x40, 0x05, 0x00, 0x04, 0x50, 0x0F, 0x02, 0x65, 0x04, 0x02, 0x00, + 0xFE, 0x40, 0x8A, 0x82, 0x30, 0x02, 0x0A, 0x40, 0x03, 0x0A, 0x05, 0x54, 0x03, 0x08, 0x6E, 0x05, + 0x25, 0x1E, 0x02, 0x0F, 0x0E, 0x00, 0xF5, 0x24, 0x20, 0x00, 0x02, 0xFE, 0xEF, 0xFD, 0xFD, 0xF3, + 0x0E, 0x0B, 0x00, 0xF7, 0x25, 0x25, 0xF0, 0x30, 0xE0, 0x7F, 0x00, 0x25, 0x14, 0x00, 0x00, 0x0B, + 0x34, 0x02, 0x0C, 0x3C, 0x02, 0x05, 0x00, 0xFB, 0x04, 0x00, 0x00, 0x25, 0x34, 0x14, 0x00, 0xFA, + 0x52, 0x0A, 0x03, 0x02, 0x6B, 0x04, 0x50, 0x02, 0x09, 0x43, 0x02, 0x12, 0x40, 0x30, 0x02, 0x12, + 0x40, 0x02, 0x1B, 0x05, 0x54, 0x18, 0x00, 0x40, 0x0C, 0xFE, 0x20, 0x30, 0x03, 0x00, 0xFA, 0x23, + 0xF2, 0x0E, 0x0E, 0x04, 0x02, 0x65, 0x28, 0xF4, 0xF7, 0x44, 0xED, 0xE3, 0x20, 0x00, 0xFD, 0xFE, + 0xFC, 0xFD, 0x03, 0x02, 0xE7, 0xF6, 0x30, 0x20, 0x3E, 0xDD, 0xCD, 0xCE, 0xD0, 0x20, 0xD0, 0x69, + 0xE0, 0x02, 0x6D, 0x3F, 0x00, 0x02, 0x6E, 0xFC, 0x03, 0x30, 0x37, 0x71, 0x02, 0xDD, 0xFE, 0xEE, + 0xEE, 0x0A, 0x0F, 0x6E, 0x0F, 0x6E, 0x0F, 0x6E, 0x17, 0x82, 0x0F, 0x6E, 0x0F, 0x6E, 0x08, 0x6E, + 0x20, 0x02, 0x6E, 0x00, 0x0D, 0x0E, 0x0F, 0x06, 0xCF, 0x04, 0x00, 0xFF, 0xE0, 0x0D, 0x07, 0xD3, + 0xAE, 0xC0, 0x13, 0x07, 0xD3, 0x02, 0x09, 0x05, 0xD3, 0x17, 0x05, 0xD3, 0xFB, 0x33, 0x54, 0x0E, + 0x05, 0x0F, 0x0D, 0x00, 0xFB, 0x33, 0xC6, 0x06, 0xD1, 0x32, 0x16, 0xED, 0x02, 0xD1, 0xF3, 0x0E, + 0x12, 0xAC, 0x0B, 0xD1, 0x38, 0x00, 0x21, 0x43, 0xC0, 0x02, 0x17, 0xF5, 0x14, 0x30, 0x00, 0x0D, + 0xD0, 0xD0, 0xC5, 0x00, 0x00, 0xED, 0x0C, 0x17, 0x36, 0x12, 0x17, 0x36, 0x02, 0x09, 0x15, 0x36, + 0x16, 0x0D, 0x63, 0x0C, 0x00, 0xFC, 0x43, 0x29, 0x06, 0x62, 0x12, 0xFD, 0xEE, 0x02, 0x62, 0xDE, + 0x0C, 0x1D, 0x33, 0x31, 0x00, 0x22, 0x07, 0xC4, 0xFA, 0x15, 0xFF, 0x05, 0xC5, 0x27, 0x03, 0x23, + 0x68, 0x25, 0x03, 0x22, 0x72, 0xEF, 0xA3, 0x26, 0x03, 0x02, 0x65, 0x13, 0xFE, 0x07, 0xC8, 0x0E, + 0x0F, 0xC8, 0x22, 0x05, 0x1F, 0x99, 0x22, 0x08, 0x14, 0x2A, 0x10, 0x00, 0xFC, 0x23, 0xC5, 0x07, + 0x12, 0x29, 0x46, 0xE7, 0x0E, 0x25, 0xC9, 0x12, 0xE8, 0x43, 0x54, 0x1A, 0x24, 0xC9, 0x1C, 0x45, + 0xE5, 0x18, 0xF3, 0x22, 0x89, 0x20, 0xD3, 0x1F, 0xF4, 0x02, 0x43, 0x1C, 0xF4, 0xBE, 0xF9, 0x4D, + 0x33, 0x2C, 0x14, 0xF4, 0x37, 0xA7, 0x16, 0xF4, 0x04, 0x69, 0x17, 0x0F, 0x65, 0x0F, 0x65, 0x0F, + 0x10, 0x2F, 0x58, 0x0F, 0x64, 0x07, 0x64, 0x53, 0xC8, 0x02, 0xEB, 0xFF, 0x5B, 0x2C, 0xC6, 0x0F, + 0x64, 0x0F, 0xC9, 0x0F, 0x64, 0x0F, 0xC8, 0x0F, 0xC8, 0x3F, 0xF4, 0x3F, 0xF4, 0x3F, 0xF4, 0x59, + 0xA4, 0x02, 0x12, 0x2E, 0x3B, 0x86, 0x0C, 0x3C, 0x86, 0x2C, 0x00, 0x00, 0x00, +}; +static const uint kFIX_0622_LEN = 1053u; + +static bool vecEqualsBlob(const Common::Array &v, const byte *blob, uint blobLen) { + if (v.size() != blobLen) return false; + if (blobLen == 0) return true; + return memcmp(v.begin(), blob, blobLen) == 0; +} + +static UcmpResult ucmpDecompress(const Common::Array &wrapped) { + UcmpResult r; + r.ok = false; + r.error.clear(); + r.expectedOut = 0; + r.producedOut = 0; + r.innerMethod = 0; + r.out.clear(); + + if (wrapped.size() < 8) { + r.error = "tooSmall"; + return r; + } + + uint32 expected = rd32le(&wrapped[0]); + r.expectedOut = expected; + if (expected == 0u || expected > (64u * 1024u * 1024u)) { + r.error = "badExpected"; + return r; + } + + const byte *stream = wrapped.begin() + 4; + size_t streamLen = wrapped.size() - 4; + + if (streamLen < 4) { + r.error = "innerHeaderTruncated"; + return r; + } + + byte method = stream[0]; + r.innerMethod = method; + if (!(method == 0u || method == 1u)) { + r.error = "badMethod"; + return r; + } + + size_t src = 4; + + if (method == 1u) { + r.out.resize((uint)(streamLen - src)); + if (streamLen > src) memcpy(r.out.begin(), stream + src, streamLen - src); + r.producedOut = (uint32)r.out.size(); + r.ok = (r.producedOut == expected); + if (!r.ok) r.error = "lenMismatch"; + return r; + } + + Common::Array out; + out.reserve(expected); + + uint16 ctrl = 0; + int bitsLeft = 0; + + while (src < streamLen && out.size() < (size_t)expected) { + if (bitsLeft == 0) { + if (src + 2 > streamLen) break; + byte lo = stream[src++]; + byte hi = stream[src++]; + ctrl = (uint16)lo | ((uint16)hi << 8); + bitsLeft = 16; + } + + uint16 bit = (uint16)(ctrl & 1u); + ctrl >>= 1; + bitsLeft--; + + if (bit == 0u) { + if (src >= streamLen) break; + out.push_back(stream[src++]); + } else { + if (src + 2 > streamLen) break; + byte t = stream[src++]; + byte b = stream[src++]; + + uint32 len = (uint32)(t & 0x0Fu) + 1u; + uint32 off = (uint32)b + ((uint32)(t & 0xF0u) << 4); + + if (off == 0u || off > out.size()) { + r.error = "backrefOob"; + return r; + } + + for (uint32 i = 0; i < len && out.size() < (size_t)expected; i++) { + out.push_back(out[out.size() - off]); + } + } + + if (out.size() > (size_t)expected + 0x100000u) { + r.error = "runawayOutput"; + return r; + } + } + + if (out.size() + 1u == (size_t)expected) { + out.push_back(0x00); + } + + r.out = out; + r.producedOut = (uint32)r.out.size(); + r.ok = (r.producedOut == expected); + if (!r.ok) r.error = "lenMismatch"; + return r; +} + +AdfImage::AdfImage(const Common::String &path) : _path(path), _originalSize(0) { + _data = readFileBytes(path); + + if (_data.size() == 814080) { + _originalSize = _data.size(); + _data.resize(819200); memset(_data.begin() + _originalSize, 0, 819200 - _originalSize); + } else if (_data.size() == 819200) { + _originalSize = _data.size(); + } else { + error("Unsupported ADF size (expected 819200 or 814080 bytes): %s", path.c_str()); + } +} + +Common::Array AdfImage::slice(uint off, uint len) const { + if (len == 0) return Common::Array(); + Common::Array out; + out.resize((uint)len); + memset(out.begin(), 0, len); + if (off >= _data.size()) return out; + size_t avail = _data.size() - off; + size_t take = (avail < len) ? avail : len; + if (take > 0) { + memcpy(out.begin(), _data.begin() + off, take); + } + return out; +} + +static Common::String readAdfsName(const byte *p, size_t n) { + Common::String s; + for (size_t i = 0; i < n; i++) { + unsigned char uc = (unsigned char)p[i]; + if (uc == 0) break; + if (uc <= 31) break; + char c = (char)uc; + s += c; + } + return trimSpaces(s); +} + +static bool disk10IsHugoNickDirBlock(const Common::Array &blk) { + if (blk.size() < 2048) return false; + char tag1[5]; + memcpy(tag1, &blk[1], 4); + tag1[4] = 0; + if (!(Common::String(tag1) == "Hugo" || Common::String(tag1) == "Nick")) return false; + char tag2[5]; + memcpy(tag2, &blk[0x7FB], 4); + tag2[4] = 0; + if (Common::String(tag2) != Common::String(tag1)) return false; + return true; +} + +static Common::String disk10CleanName10(const byte *p10) { + Common::String s; + for (int i = 0; i < 10; i++) { + byte c = (byte)(p10[i] & 0x7F); + if (c == 0) break; + if (c == 0x0D) break; + if (c < 32) break; + s += (char)c; + } + while (!s.empty() && s.lastChar() == ' ') s.deleteLastChar(); + return s; +} + +static Disk10OldDirEntry disk10BrutefindLeafEntry(const AdfImage &img, const Common::String &leafName) { + Disk10OldDirEntry r; + r.ok = false; + r.length = 0; + r.sector = 0; + + const Common::String want = toLower(trimSpaces(leafName)); + const size_t dirSize = 2048; + const size_t entryBase = 0x05; + const size_t entrySize = 0x1A; + const size_t numentries = 77; + + for (size_t off = 0; off + dirSize <= img.size(); off += 256) { + Common::Array blk = img.slice(off, dirSize); + if (!disk10IsHugoNickDirBlock(blk)) continue; + + for (size_t i = 0; i < numentries; i++) { + size_t eo = entryBase + i * entrySize; + if (eo + entrySize > blk.size()) break; + if (blk[eo + 0] == 0) continue; + + Common::String nm = disk10CleanName10(&blk[eo + 0]); + if (nm.empty()) continue; + if (toLower(nm) != want) continue; + + uint32 len = rd32le(&blk[eo + 0x12]); + uint32 sec = rd24le(&blk[eo + 0x16]); + if (len == 0 || sec == 0) continue; + + r.ok = true; + r.length = len; + r.sector = sec; + return r; + } + } + + return r; +} + +static Common::Array disk10BrutefindLeafEntries(const AdfImage &img, const Common::String &leafName) { + Common::Array out; + + const Common::String want = toLower(trimSpaces(leafName)); + const size_t dirSize = 2048; + const size_t entryBase = 0x05; + const size_t entrySize = 0x1A; + const size_t numentries = 77; + + for (size_t off = 0; off + dirSize <= img.size(); off += 256) { + Common::Array blk = img.slice(off, dirSize); + if (!disk10IsHugoNickDirBlock(blk)) continue; + + for (size_t i = 0; i < numentries; i++) { + size_t eo = entryBase + i * entrySize; + if (eo + entrySize > blk.size()) break; + if (blk[eo + 0] == 0) continue; + + Common::String nm = disk10CleanName10(&blk[eo + 0]); + if (nm.empty()) continue; + if (toLower(nm) != want) continue; + + uint32 len = rd32le(&blk[eo + 0x12]); + uint32 sec = rd24le(&blk[eo + 0x16]); + if (len == 0 || sec == 0) continue; + + Disk10OldDirEntry e; + e.ok = true; + e.length = len; + e.sector = sec; + out.push_back(e); + } + } + + return out; +} + +static HugoNickDiscAddrEntry hugonickBrutefindLeafDiscaddr(const AdfImage &img, const Common::String &leafName) { + HugoNickDiscAddrEntry r; + r.ok = false; + r.length = 0; + r.discaddr = 0; + r.attr = 0; + + const Common::String want = toLower(trimSpaces(leafName)); + const size_t dirSize = 2048; + const size_t entryBase = 0x05; + const size_t entrySize = 0x1A; + + for (size_t off = 0; off + dirSize <= img.size(); off += 256) { + Common::Array blk = img.slice(off, dirSize); + if (!disk10IsHugoNickDirBlock(blk)) continue; + + const size_t maxEntries = (blk.size() - entryBase) / entrySize; + for (size_t i = 0; i < maxEntries; i++) { + size_t eo = entryBase + i * entrySize; + if (eo + entrySize > blk.size()) break; + const byte *e = &blk[eo]; + + Common::String nm = disk10CleanName10(e + 0x00); + if (nm.empty()) continue; + if (toLower(nm) != want) continue; + + uint32 len = rd32le(e + 0x12); + uint32 discaddr = rd24le(e + 0x16); + byte attr = e[0x19]; + if (discaddr == 0) continue; + + r.ok = true; + r.length = len; + r.discaddr = discaddr; + r.attr = attr; + return r; + } + } + + return r; +} + +AdfsVolume::AdfsVolume(const AdfImage &img, bool requireSimon) : _img(img), _valid(false) { + if (!tryInit(requireSimon)) { + if (requireSimon) + error("Failed to parse ADFS volume or locate !Simon directory in: %s", img.path().c_str()); + } +} + +bool AdfsVolume::tryInit(bool requireSimon) { + if (!tryParseDiscRecord()) return false; + + _rootDirDiscaddr = _bootmap + (_nzones * _secSize * 2u); + _rootParentDirDiscaddr = _rootDirDiscaddr; + + _simonDirDiscaddr = 0xFFFFFFFFu; + Common::Array rootEnts = listDirByDiscaddr(_rootParentDirDiscaddr); + for (const AdfsObject &e : rootEnts) { + if (ieq(e.name, "!Simon") && e.isDir) { + _simonDirDiscaddr = e.startDiscaddr; + break; + } + } + if (_simonDirDiscaddr == 0xFFFFFFFFu) { + if (requireSimon) return false; + _simonDirDiscaddr = 0u; + } + _valid = true; + return true; +} + +bool AdfsVolume::findPath(const Common::String &adfsPath, AdfsObject &outObj) const { + Common::StringList parts = splitDotPath(adfsPath); + if (parts.empty()) return false; + + uint32 dirDiscaddr = _rootParentDirDiscaddr; + + for (size_t i = 0; i < parts.size(); i++) { + const Common::String &want = parts[i]; + Common::Array ents = listDirByDiscaddr(dirDiscaddr); + bool found = false; + AdfsObject got{}; + + for (const AdfsObject &e : ents) { + if (ieq(e.name, want)) { + found = true; + got = e; + break; + } + } + if (!found) return false; + + if (i + 1 == parts.size()) { + outObj = got; + return true; + } + + if (!got.isDir) return false; + dirDiscaddr = got.startDiscaddr; + } + + return false; +} + +Common::Array AdfsVolume::readFile(const AdfsObject &f) const { + if (f.isDir) { error("readFile called on dir"); return Common::Array(); } + + Common::Array frags = discaddrToFrags(f.startDiscaddr); + if (frags.empty()) { + uint32 fragId = (f.startDiscaddr / 0x100u) & 0xFFFFu; + uint32 sector = f.startDiscaddr & 0xFFu; + uint32 secAdj = (sector > 0) ? (sector - 1u) : 0u; + uint64 guessOff = (uint64)fragId * (uint64)_secSize + (uint64)secAdj * (uint64)_secSize; + if (guessOff + f.length <= _img.size()) { + return _img.slice((size_t)guessOff, (size_t)f.length); + } + error("ADFS map lookup failed for file start address: %s", f.name.c_str()); return Common::Array(); + } + + Common::Array out; + out.reserve(f.length); + + uint32 remaining = f.length; + + for (const AdfsVolume::Frag &fr : frags) { + if (remaining == 0) break; + if (fr.offset >= _img.size()) break; + + uint32 canTake = fr.length; + uint32 maxAvail = (uint32)(_img.size() - fr.offset); + if (canTake > maxAvail) canTake = maxAvail; + if (canTake > remaining) canTake = remaining; + + Common::Array chunk = _img.slice(fr.offset, canTake); + out.push_back(chunk); + remaining -= canTake; + } + + if (out.size() != (size_t)f.length) { + warning("Short read: file=%s want=%u got=%u (disk=%s)", + f.name.c_str(), f.length, (uint)out.size(), _img.path().c_str()); + } + + return out; +} + +byte AdfsVolume::readU8(uint32 off) const { + Common::Array b = _img.slice(off, 1); + return b.empty() ? 0 : b[0]; +} + +uint32 AdfsVolume::readBits(uint32 baseOff, uint32 startBit, uint32 nbits) const { + uint32 res = 0; + if (nbits == 0 || nbits > 32) return 0; + + uint32 prevByteOff = 0xFFFFFFFFu; + byte lastbyte = 0; + + for (uint32 bit = 0; bit < nbits; bit++) { + uint32 byteOff = baseOff + ((startBit + bit) / 8u); + uint32 bitInByte = (startBit + bit) & 7u; + + if (byteOff != prevByteOff) { + prevByteOff = byteOff; + lastbyte = readU8(byteOff); + } + + uint32 b = (uint32)((lastbyte >> bitInByte) & 1u); + res |= (b << bit); + } + + return res; +} + +bool AdfsVolume::tryParseDiscRecord() { + Common::Array dr = _img.slice(0, 0x40); + if (dr.size() < 0x40) return false; + + byte log2sec = dr[4]; + _secSize = 1u << log2sec; + + _idlen = dr[8]; + _zoneSpareBits = (uint32)readUint16LELocal(&dr[14]); + + _bpmb = (uint32)dr[21]; + if (_bpmb == 0) _bpmb = 128; + + _discSize = 819200; + _bootmap = 0; + _nzones = 1; + + if (_secSize != 1024) { + warning("Unexpected sector size %u in %s", _secSize, _img.path().c_str()); + } + if (_idlen == 0 || _idlen > 31) return false; + if (_zoneSpareBits == 0) return false; + return true; +} + +Common::Array AdfsVolume::discaddrToFrags(uint32 addr) const { + const uint32 drSize = 0x40; + const uint32 header = 4; + + Common::Array frags; + const uint32 mapBase = _bootmap + drSize; + + uint32 idPerZone = (((_secSize * 8u) - _zoneSpareBits) / (_idlen + 1u)); + if (idPerZone == 0) return frags; + + uint32 fragId = (addr / 0x100u) & 0xFFFFu; + uint32 startZone = (((addr / 0x100u) & 0xFFFFu) / idPerZone); + + for (uint32 zonecounter = 0; zonecounter < _nzones; zonecounter++) { + uint32 zone = (zonecounter + startZone) % _nzones; + + uint32 allmap = (zone + 1u) * _secSize * 8u - drSize * 8u; + uint32 i = zone * _secSize * 8u; + if (zone > 0) i -= (drSize * 8u - header * 8u); + + while (i < allmap) { + uint32 offBits = i; + + uint32 id = readBits(mapBase, i, _idlen); + i += _idlen; + + { + uint32 j = i - 1u; + while (true) { + j++; + if (j >= allmap) break; + byte b = readU8(mapBase + (j / 8u)); + if (bitIsSet(b, j & 7u)) break; + } + + i = j; + + if (id == fragId) { + uint32 offBytes = (offBits - (_zoneSpareBits * zone)) * _bpmb; + offBytes %= _discSize; + uint32 lenBytes = (j - offBits + 1u) * _bpmb; + + Frag f; + f.offset = offBytes; + f.length = lenBytes; + frags.push_back(f); + } + } + + i++; + } + } + + if (frags.empty()) return frags; + + uint32 sector = addr & 0xFFu; + if (sector >= 1u) sector -= 1u; + + for (AdfsVolume::Frag &f : frags) { + f.offset = f.offset + sector * _secSize; + } + + return frags; +} + +Common::Array AdfsVolume::listDirByDiscaddr(uint32 dirDiscaddr) const { + Common::Array fr; + if (dirDiscaddr == _rootParentDirDiscaddr) { + Frag f; + f.offset = dirDiscaddr; + f.length = 0x800; + fr.push_back(f); + } else { + fr = discaddrToFrags(dirDiscaddr); + } + if (fr.empty()) return Common::Array(); + if (fr[0].offset >= _img.size()) return Common::Array(); + + const size_t entrySize = 0x1A; + const size_t entryBase = 5; + + Common::Array out; + out.reserve(128); + + Common::HashMap seenLower; + + auto parseBlock = [&](const Common::Array &blk) { + if (blk.size() < 0x800) return; + if (blk.size() <= entryBase) return; + + const size_t maxEntries = (blk.size() - entryBase) / entrySize; + + for (size_t i = 0; i < maxEntries; i++) { + const byte *e = &blk[entryBase + i * entrySize]; + + Common::String name = readAdfsName(e + 0x00, 10); + if (name.empty()) continue; + + uint32 start = rd24le(e + 0x16); + if (start == 0) continue; + + byte attr = e[0x19]; + bool isDir = (attr & 0x08) != 0; + + AdfsObject obj; + obj.name = trimSpaces(name); + obj.isDir = isDir; + obj.loadAddr = rd32le(e + 0x0A); + obj.execAddr = rd32le(e + 0x0E); + obj.length = rd32le(e + 0x12); + obj.startDiscaddr = start; + obj.parentDirDiscaddr = dirDiscaddr; + + Common::String low = toLower(obj.name); + if (!seenLower.contains(low)) { + seenLower[low] = true; + out.push_back(obj); + } + } + }; + + Common::Array blk0 = _img.slice(fr[0].offset, 0x800); + if (blk0.size() < 0x800) return Common::Array(); + + const bool isHugoNick = disk10IsHugoNickDirBlock(blk0); + + parseBlock(blk0); + + if (isHugoNick) { + char tag1[5]; + memcpy(tag1, &blk0[1], 4); + tag1[4] = 0; + + const uint32 maxExtraBlocks = 32; + for (uint32 b = 1; b <= maxExtraBlocks; b++) { + uint64 off = static_cast(fr[0].offset) + static_cast(b) * 0x800ull; + if (off + 0x800ull > _img.size()) break; + + Common::Array blkN = _img.slice(static_cast(off), 0x800); + if (blkN.size() < 0x800) break; + + if (!disk10IsHugoNickDirBlock(blkN)) break; + + char tagN[5]; + memcpy(tagN, &blkN[1], 4); + tagN[4] = 0; + if (Common::String(tagN) != Common::String(tag1)) break; + + parseBlock(blkN); + } + } + + Common::sort(out.begin(), out.end(), [](const AdfsObject &a, const AdfsObject &b) { + return toLower(a.name) < toLower(b.name); + }); + + return out; +} + +Common::Array AdfsVolume::listDir(const Common::String &adfsDirPath) const { + if (adfsDirPath.empty()) { + return listDirByDiscaddr(_rootParentDirDiscaddr); + } + AdfsObject dirObj; + if (!findPath(adfsDirPath, dirObj)) return Common::Array(); + if (!dirObj.isDir) return Common::Array(); + return listDirByDiscaddr(dirObj.startDiscaddr); +} + +Common::StringList AdfsVolume::splitDotPath(const Common::String &p) const { + Common::StringList parts; + Common::String cur; + for (char c : p) { + if (c == '.') { + cur = trimSpaces(cur); + if (!cur.empty()) parts.push_back(cur); + cur.clear(); + } else { + cur += c; + } + } + cur = trimSpaces(cur); + if (!cur.empty()) parts.push_back(cur); + return parts; +} + +static bool looksLikeAdfsDirBlock(const Common::Array &buf) { + if (buf.size() < 0x800) return false; + if (buf[0] != 0x00) return false; + const char *tag = (const char *)&buf[1]; + if (!((tag[0] == 'H' && tag[1] == 'u' && tag[2] == 'g' && tag[3] == 'o') || + (tag[0] == 'N' && tag[1] == 'i' && tag[2] == 'c' && tag[3] == 'k'))) { + return false; + } + const char *tag2 = (const char *)&buf[0x7FB]; + return tag2[0] == tag[0] && tag2[1] == tag[1] && tag2[2] == tag[2] && tag2[3] == tag[3]; +} + +static bool readFileAnyDisk(const Common::HashMap &vols, const Common::String &adfsFilePath, uint32 wantLen, Common::Array &outData) { + for (Common::HashMap::const_iterator it = vols.begin(); it != vols.end(); ++it) { + int disk = it->_key; + AdfsVolume *vol = it->_value; + if (!vol) continue; + if (disk == 10) continue; + AdfsObject obj; + if (!vol->findPath(adfsFilePath, obj)) continue; + if (obj.isDir) continue; + if (wantLen != 0 && obj.length != wantLen) continue; + Common::Array data = vol->readFile(obj); + if (wantLen != 0 && data.size() != (size_t)wantLen) continue; + if (looksLikeAdfsDirBlock(data)) continue; + outData = data; + return true; + } + return false; +} + +static Common::String expectedDiskFilename(int n) { + return "Simon the Sorcerer- Acorn Archimedes - (Disk " + Common::String::format("%d", n) + ").adf"; +} + +static DiskSet loadDisks(const Common::String &inputDir) { + DiskSet ds; + for (int i = 1; i <= 10; i++) { + Common::String p = joinPath(inputDir, expectedDiskFilename(i)); + if (i == 10) { + if (pathExists(p)) ds.diskPaths[i] = p; + continue; + } + if (!pathExists(p)) error("Missing required disk image: %s", p.c_str()); + ds.diskPaths[i] = p; + } + return ds; +} + +static void createInstallDirs(Tool *tool, const Common::String &outRoot) { + static const char * const kDirs[] = { + "!Simon", "!Simon/Execute", "!Simon/Tables", "!Simon/Text", "!Simon/Tunes", + "!Simon/00", "!Simon/01", "!Simon/02", "!Simon/03", "!Simon/04", "!Simon/05", + "!Simon/06", "!Simon/07", "!Simon/08", "!Simon/09", "!Simon/10", "!Simon/11", + "!Simon/12", "!Simon/13", "!Simon/14", "!Simon/15", "!Simon/16", + }; + + for (int _di = 0; _di < ARRAYSIZE(kDirs); _di++) ensureDir(tool, joinPath(outRoot, kDirs[_di])); + +} + +static int diskNumberFromScriptPrefix(const Common::String &spec) { + Common::String s = spec; + const char *hit = strstr(s.c_str(), "ADFS::Simon"); + if (!hit) return -1; + const char *q = hit + 11; + int n = 0; + while (*q && isdigit((unsigned char)*q)) { + n = n * 10 + (*q - '0'); + q++; + } + if (n <= 0) { + return -1; + } + return n; +} + +static Common::String adfsPathFromScript(const Common::String &spec) { + Common::String s = trimSpaces(spec); + const char *hit = strstr(s.c_str(), "!Simon"); + if (!hit) { + return ""; + } + return Common::String(hit); +} + +static Common::String normalizeDestPrefix(const Common::String &destPrefix) { + Common::String s = trimSpaces(destPrefix); + while (!s.empty() && s.lastChar() == '.') s.deleteLastChar(); + for (uint ci = 0; ci < s.size(); ci++) { + if (s[ci] == '.') s.setChar('/', ci); + } + return s; +} + +static void copyGlobFromAdfs(Tool *tool, const Common::HashMap &vols, const AdfsVolume &vol, const Common::String &adfsDirPath, const Common::String &pattern, const Common::String &outDir, const Common::String &relOutDir, int diskForLog) { + Common::Array entries = vol.listDir(adfsDirPath); + for (const AdfsObject &e : entries) { + if (e.isDir) { + continue; + } + if (e.name == "!Boot" || e.name == "!Run" || e.name == "!Sprites") { + continue; + } + if (!matchStarPatternICase(e.name, pattern)) { + continue; + } + Common::Array data = vol.readFile(e); + + if (data.size() != (size_t)e.length || looksLikeAdfsDirBlock(data)) { + Common::Array alt; + Common::String fullPath = adfsDirPath + "." + e.name; + if (readFileAnyDisk(vols, fullPath, e.length, alt)) { + data = alt; alt.clear(); + } + } + Common::String outPath = joinPath(outDir, e.name); + if (writeFileBytesIfBetter(tool, outPath, data)) { + if (!relOutDir.empty() && diskForLog > 0) logSuccess(relOutDir + "/" + e.name, "raw", diskForLog); + } + } +} + +static void ucmpGlobFromAdfs(Tool *tool, const Common::HashMap &vols, const AdfsVolume &vol, const Common::String &adfsDirPath, const Common::String &pattern, const Common::String &outDir, const Common::String &relOutDir, int diskForLog) { + Common::Array entries = vol.listDir(adfsDirPath); + for (const AdfsObject &e : entries) { + if (e.isDir) { + continue; + } + if (!matchStarPatternICase(e.name, pattern)) { + continue; + } + + if (e.name == "!Boot" || e.name == "!Run" || e.name == "!Sprites") { + continue; + } + + Common::Array data = vol.readFile(e); + + if (data.size() != (size_t)e.length || looksLikeAdfsDirBlock(data)) { + Common::Array alt; + Common::String fullPath = adfsDirPath + "." + e.name; + if (readFileAnyDisk(vols, fullPath, e.length, alt)) { + data = alt; alt.clear(); + } + } + + bool ok = false; + UcmpResult last{}; + Common::Array out; + + const size_t tryOffs[] = { 8, 4, 0 }; + for (size_t off : tryOffs) { + if (data.size() <= off) continue; + Common::Array view; + if (data.size() > off) { view.resize((uint)(data.size() - off)); memcpy(view.begin(), data.begin() + off, data.size() - off); } + UcmpResult res = ucmpDecompress(view); + last = res; + if (res.ok) { + ok = true; + out = res.out; + break; + } + } + + Common::String outPath = joinPath(outDir, e.name); + if (ok) { + if (writeFileBytesIfBetter(tool, outPath, out)) { + if (!relOutDir.empty() && diskForLog > 0) logSuccess(relOutDir + "/" + e.name, "ucmp", diskForLog); + } + } else { + warning("UCMP decompress failed, writing raw: %s.%s expected=%u got=%u method=%d err=%s", + adfsDirPath.c_str(), e.name.c_str(), last.expectedOut, last.producedOut, (int)last.innerMethod, last.error.c_str()); + writeFileBytesIfBetter(tool, outPath, data); + if (!relOutDir.empty() && diskForLog > 0) logSuccess(relOutDir + "/" + e.name, "raw", diskForLog); + } + } +} + +static void runInstallScriptFake(Tool *tool, const Common::HashMap &vols, const Common::HashMap &images, const Common::String &outRoot) { + createInstallDirs(tool, outRoot); + + auto getVol = [&](int disk) -> const AdfsVolume& { + Common::HashMap::const_iterator it = vols.find(disk); + if (it == vols.end() || it->_value == nullptr) { error("Disk not loaded: %d", disk); } + return *it->_value; + }; + + auto COPY_FILE_glob = [&](const Common::String &destPrefix, const Common::String &srcSpec, const Common::String &patternOverride) { + int disk = diskNumberFromScriptPrefix(srcSpec); + Common::String adfsPath = adfsPathFromScript(srcSpec); + Common::String dirPath = adfsPath; + Common::String patt = patternOverride; + { + int _ld = -1; + for (int _i = (int)adfsPath.size() - 1; _i >= 0; _i--) { + if (adfsPath[_i] == '.') { _ld = _i; break; } + } + if (_ld >= 0) dirPath = Common::String(adfsPath.c_str(), adfsPath.c_str() + _ld); + } + Common::String outDir = normalizeDestPrefix(destPrefix); + Common::String outPathDir = joinPath(outRoot, outDir); + copyGlobFromAdfs(tool, vols, getVol(disk), dirPath, patt, outPathDir, outDir, disk); + }; + + auto UCMP_FILE_glob = [&](const Common::String &destPrefix, const Common::String &srcSpec, const Common::String &patternOverride) { + int disk = diskNumberFromScriptPrefix(srcSpec); + Common::String adfsPath = adfsPathFromScript(srcSpec); + Common::String dirPath = adfsPath; + Common::String patt = patternOverride; + { + int _ld = -1; + for (int _i = (int)adfsPath.size() - 1; _i >= 0; _i--) { + if (adfsPath[_i] == '.') { _ld = _i; break; } + } + if (_ld >= 0) dirPath = Common::String(adfsPath.c_str(), adfsPath.c_str() + _ld); + } + Common::String outDir = normalizeDestPrefix(destPrefix); + Common::String outPathDir = joinPath(outRoot, outDir); + ucmpGlobFromAdfs(tool, vols, getVol(disk), dirPath, patt, outPathDir, outDir, disk); + }; + + // Disk 1 + COPY_FILE_glob("!Simon.Execute.", "ADFS::Simon1.!Simon.Execute.*", "*"); + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon1.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon1.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon1.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.01.", "ADFS::Simon1.!Simon.01.*", "*"); + UCMP_FILE_glob("!Simon.06.", "ADFS::Simon1.!Simon.06.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon1.!Simon.12.*", "*"); + UCMP_FILE_glob("!Simon.13.", "ADFS::Simon1.!Simon.13.*", "*"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon1.!Simon.14.*", "*"); + UCMP_FILE_glob("!Simon.15.", "ADFS::Simon1.!Simon.15.*", "*"); + UCMP_FILE_glob("!Simon.16.", "ADFS::Simon1.!Simon.16.*", "*"); + + // Disk 2 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon2.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon2.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon2.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.00.", "ADFS::Simon2.!Simon.00.*", "*"); + UCMP_FILE_glob("!Simon.03.", "ADFS::Simon2.!Simon.03.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon2.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.06.", "ADFS::Simon2.!Simon.06.*", "*"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1401", "1401"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1402", "1402"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1411", "1411"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1412", "1412"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1421", "1421"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon2.!Simon.14.1422", "1422"); + + // Disk 3 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon3.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon3.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon3.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.01.", "ADFS::Simon3.!Simon.01.*", "*"); + UCMP_FILE_glob("!Simon.04.", "ADFS::Simon3.!Simon.04.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon3.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.07.", "ADFS::Simon3.!Simon.07.*", "*"); + UCMP_FILE_glob("!Simon.08.", "ADFS::Simon3.!Simon.08.*", "*"); + UCMP_FILE_glob("!Simon.09.", "ADFS::Simon3.!Simon.09.*", "*"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon3.!Simon.10.*", "*"); + + // Disk 4 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon4.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon4.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon4.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.04.", "ADFS::Simon4.!Simon.04.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon4.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.06.", "ADFS::Simon4.!Simon.06.*", "*"); + UCMP_FILE_glob("!Simon.08.", "ADFS::Simon4.!Simon.08.*", "*"); + UCMP_FILE_glob("!Simon.09.", "ADFS::Simon4.!Simon.09.*", "*"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon4.!Simon.10.1031", "1031"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon4.!Simon.10.1032", "1032"); + UCMP_FILE_glob("!Simon.11.", "ADFS::Simon4.!Simon.11.*", "*"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon4.!Simon.14.*", "*"); + + // Disk 5 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon5.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon5.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon5.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.00.", "ADFS::Simon5.!Simon.00.*", "*"); + UCMP_FILE_glob("!Simon.01.", "ADFS::Simon5.!Simon.01.*", "*"); + UCMP_FILE_glob("!Simon.02.", "ADFS::Simon5.!Simon.02.*", "*"); + UCMP_FILE_glob("!Simon.08.", "ADFS::Simon5.!Simon.08.*", "*"); + + // Disk 6 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon6.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon6.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon6.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.01.", "ADFS::Simon6.!Simon.01.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon6.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.06.", "ADFS::Simon6.!Simon.06.*", "*"); + UCMP_FILE_glob("!Simon.07.", "ADFS::Simon6.!Simon.07.*", "*"); + UCMP_FILE_glob("!Simon.08.", "ADFS::Simon6.!Simon.08.*", "*"); + UCMP_FILE_glob("!Simon.09.", "ADFS::Simon6.!Simon.09.*", "*"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon6.!Simon.10.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon6.!Simon.12.*", "*"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon6.!Simon.14.*", "*"); + + // Disk 7 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon7.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon7.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon7.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.03.", "ADFS::Simon7.!Simon.03.*", "*"); + UCMP_FILE_glob("!Simon.04.", "ADFS::Simon7.!Simon.04.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon7.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.06.", "ADFS::Simon7.!Simon.06.*", "*"); + UCMP_FILE_glob("!Simon.09.", "ADFS::Simon7.!Simon.09.*", "*"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon7.!Simon.10.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon7.!Simon.12.*", "*"); + + // Disk 8 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon8.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon8.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon8.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.00.", "ADFS::Simon8.!Simon.00.*", "*"); + UCMP_FILE_glob("!Simon.02.", "ADFS::Simon8.!Simon.02.*", "*"); + UCMP_FILE_glob("!Simon.05.", "ADFS::Simon8.!Simon.05.*", "*"); + UCMP_FILE_glob("!Simon.11.", "ADFS::Simon8.!Simon.11.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon8.!Simon.12.*", "*"); + UCMP_FILE_glob("!Simon.13.", "ADFS::Simon8.!Simon.13.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon8.!Simon.12.*", "*"); + + // Disk 9 + COPY_FILE_glob("!Simon.Tunes.", "ADFS::Simon9.!Simon.Tunes.*Tune", "*Tune"); + COPY_FILE_glob("!Simon.Tables.", "ADFS::Simon9.!Simon.Tables.Tables*", "Tables*"); + COPY_FILE_glob("!Simon.Text.", "ADFS::Simon9.!Simon.Text.Text*", "Text*"); + UCMP_FILE_glob("!Simon.00.", "ADFS::Simon9.!Simon.00.*", "*"); + UCMP_FILE_glob("!Simon.09.", "ADFS::Simon9.!Simon.09.*", "*"); + UCMP_FILE_glob("!Simon.10.", "ADFS::Simon9.!Simon.10.*", "*"); + UCMP_FILE_glob("!Simon.12.", "ADFS::Simon9.!Simon.12.*", "*"); + UCMP_FILE_glob("!Simon.14.", "ADFS::Simon9.!Simon.14.*", "*"); + UCMP_FILE_glob("!Simon.15.", "ADFS::Simon9.!Simon.15.*", "*"); + UCMP_FILE_glob("!Simon.16.", "ADFS::Simon9.!Simon.16.*", "*"); + + { + auto pathExists = [&](const Common::String &p) -> bool { + FILE *f2 = fopen(p.c_str(), "rb"); + if (!f2) return false; + fclose(f2); + return true; + }; + + auto recoverOne = [&](int disk, const Common::String &relOutDir, const Common::String &leaf, bool tryUcmp) { + Common::String outDir = joinPath(outRoot, relOutDir); + ensureDir(tool, outDir); + Common::String outPath = joinPath(outDir, leaf); + if (pathExists(outPath)) return; + + Common::HashMap::const_iterator itImg = images.find(disk); + if (itImg == images.end() || itImg->_value == nullptr) { + warning("Disk image not loaded for disk %d while extracting %s/%s", disk, relOutDir.c_str(), leaf.c_str()); + return; + } + const AdfImage &img = *itImg->_value; + + Common::Array data; + + { + HugoNickDiscAddrEntry he = hugonickBrutefindLeafDiscaddr(img, leaf); + if (he.ok) { + Common::HashMap::const_iterator itVol = vols.find(disk); + if (itVol != vols.end() && itVol->_value != nullptr) { + AdfsObject fake; + fake.name = leaf; + fake.isDir = false; + fake.loadAddr = 0; + fake.execAddr = 0; + fake.length = he.length; + fake.startDiscaddr = he.discaddr; + fake.parentDirDiscaddr = 0; + + data = itVol->_value->readFile(fake); + } + } + } + + if (data.empty()) { + Disk10OldDirEntry fe = disk10BrutefindLeafEntry(img, leaf); + if (fe.ok) { + uint32 byteOff = fe.sector * 256u; + if ((size_t)byteOff + (size_t)fe.length <= img.size()) { + data = img.slice((size_t)byteOff, (size_t)fe.length); + } + } + } + + if (data.empty()) { + warning("Leaf not found on disk %d: %s/%s", disk, relOutDir.c_str(), leaf.c_str()); + return; + } + + if (!tryUcmp) { + writeFileBytes(tool, outPath, data); + logSuccess(relOutDir + "/" + leaf, "raw", disk); + return; + } + + bool ok = false; + UcmpResult last{}; + Common::Array out; + const size_t tryOffs[] = { 8u, 4u, 0u }; + for (size_t ti = 0; ti < 3; ti++) { + size_t off = tryOffs[ti]; + if (data.size() <= off) continue; + Common::Array view; + if (data.size() > off) { view.resize((uint)(data.size() - off)); memcpy(view.begin(), data.begin() + off, data.size() - off); } + UcmpResult res = ucmpDecompress(view); + last = res; + if (res.ok) { + ok = true; + out = res.out; + break; + } + } + + if (ok) { + writeFileBytes(tool, outPath, out); + logSuccess(relOutDir + "/" + leaf, "ucmp", disk); + } else { + writeFileBytes(tool, outPath, data); + warning("UCMP failed, wrote raw: %s/%s expected=%u got=%u method=%d err=%s (disk %d)", + relOutDir.c_str(), leaf.c_str(), last.expectedOut, last.producedOut, (int)last.innerMethod, last.error.c_str(), disk); + logSuccess(relOutDir + "/" + leaf, "raw", disk); + } + }; + + { + const char *names[] = { "0001","0002","0011","0012","0021","0022","0071","0072","0081","0082" }; + for (size_t i = 0; i < sizeof(names)/sizeof(names[0]); i++) recoverOne(1, "!Simon/00", names[i], true); + } + + { + const char *names[] = { "0119","0141","0142","0151","0152","0161","0162","0171","0172","0181","0182","0191","0192" }; + for (size_t i = 0; i < sizeof(names)/sizeof(names[0]); i++) recoverOne(5, "!Simon/01", names[i], true); + } + + { + const char *names[] = { "0391","0392" }; + for (size_t i = 0; i < sizeof(names)/sizeof(names[0]); i++) recoverOne(7, "!Simon/03", names[i], true); + } + + { + const char *names[] = { "2TUNE","3TUNE","21TUNE","26TUNE","27TUNE" }; + for (size_t i = 0; i < sizeof(names)/sizeof(names[0]); i++) recoverOne(6, "!Simon/Tunes", names[i], false); + } + + } + + bool disk10Present = false; + { + const AdfImage *img10 = nullptr; + { + Common::HashMap::const_iterator itImg = images.find(10); + if (itImg != images.end()) img10 = itImg->_value; + } + + Common::HashMap::const_iterator it10 = vols.find(10); + const AdfsVolume *v10 = (it10 != vols.end()) ? it10->_value : nullptr; + + if (img10 != nullptr) { + disk10Present = true; + Common::String out06 = joinPath(outRoot, "!Simon/06"); + ensureDir(tool, out06); + + const char *names[2] = { "0621", "0622" }; + for (int i = 0; i < 2; i++) { + const char *leaf = names[i]; + + Common::Array data; + + if (v10 != nullptr) { + AdfsObject obj; + const Common::String p1 = Common::String("!Update.Resources.Disc6.") + leaf; + const Common::String p2 = Common::String("!Update.Resources.Disk6.") + leaf; + + if ((v10->findPath(p1, obj) && !obj.isDir) || (v10->findPath(p2, obj) && !obj.isDir)) { + data = v10->readFile(obj); + } + } + + if (data.empty()) { + Common::Array cands = disk10BrutefindLeafEntries(*img10, leaf); + for (size_t ci = 0; ci < cands.size(); ci++) { + const Disk10OldDirEntry &ce = cands[ci]; + if (!ce.ok) continue; + uint32 byteOff = ce.sector * 256u; + if ((size_t)byteOff + (size_t)ce.length > img10->size()) continue; + + Common::Array cand = img10->slice((size_t)byteOff, (size_t)ce.length); + + if (ieq(leaf, "0621") && vecEqualsBlob(cand, kFIX_0621, kFIX_0621_LEN)) { + data = cand; + break; + } + if (ieq(leaf, "0622") && vecEqualsBlob(cand, kFIX_0622, kFIX_0622_LEN)) { + data = cand; + break; + } + if (data.empty() && !cand.empty()) data = cand; + } + } + + if (data.empty()) { + warning("Disk 10 update file not found or unreadable: !Update.Resources.Disc6.%s", leaf); + continue; + } + + Common::String outPath = joinPath(out06, Common::String(leaf)); + writeFileBytes(tool, outPath, data); + logSuccess(Common::String("!Simon/06/") + leaf, "raw-update", 10); + } + } + } + { + { + Common::String p = joinPath(outRoot, "!Simon/06/0621"); + if (pathExists(p)) { + Common::Array cur = readFileBytes(p); + if (!vecEqualsBlob(cur, kFIX_0621, kFIX_0621_LEN)) { + Common::Array good(kFIX_0621, (int)kFIX_0621_LEN); + writeFileBytes(tool, p, good); + logSuccess("!Simon/06/0621", "patched", disk10Present ? "disk 10" : "files generated"); + } + } + } + + { + Common::String p = joinPath(outRoot, "!Simon/06/0622"); + if (pathExists(p)) { + Common::Array cur = readFileBytes(p); + if (!vecEqualsBlob(cur, kFIX_0622, kFIX_0622_LEN)) { + Common::Array good(kFIX_0622, (int)kFIX_0622_LEN); + writeFileBytes(tool, p, good); + logSuccess("!Simon/06/0622", "patched", disk10Present ? "disk 10" : "files generated"); + } + } + } + } +} + + +ExtractSimonAcorn::ExtractSimonAcorn(const std::string &name) : Tool(name, TOOLTYPE_EXTRACTION) { + ToolInput input; + input.format = "*"; + input.file = false; + _inputPaths.push_back(input); + + _shorthelp = "Extract Simon the Sorcerer Acorn Archimedes data from installer ADF disk images."; + + _helptext = + "Reads a set of ADF disk images and extracts the game data directly.\n" + "Decompresses UCMP-compressed scene files as needed. This avoids\n" + "having to run the original installer inside an emulator.\n" + "\n" + "Expected disk images (Disk 10 is the optional update disk):\n" + " Simon the Sorcerer- Acorn Archimedes - (Disk 1).adf\n" + " ...\n" + " Simon the Sorcerer- Acorn Archimedes - (Disk 10).adf\n" + "\n" + "Usage:\n" + " scummvm-tools-cli --tool extract_simon_acorn --input-dir --output-dir \n"; +} + +void ExtractSimonAcorn::parseExtraArguments() { + if (_arguments.empty() || _arguments.front() != "--input-dir") + error("Missing required argument: --input-dir"); + + _arguments.pop_front(); + if (_arguments.empty()) + error("Missing value for --input-dir"); + + std::string inputDir = _arguments.front(); + _arguments.pop_front(); + + if (_arguments.empty() || _arguments.front() != "--output-dir") + error("Missing required argument: --output-dir"); + + _arguments.pop_front(); + if (_arguments.empty()) + error("Missing value for --output-dir"); + + _outputPath = _arguments.front(); + _arguments.pop_front(); + + if (!_arguments.empty()) + error("Unexpected extra argument: %s", _arguments.front().c_str()); + + _arguments.push_back(inputDir); +} + +void ExtractSimonAcorn::execute() { + if (_inputPaths.empty() || _inputPaths[0].path.empty()) + error("Missing input directory."); + + if (_outputPath.empty()) + error("Missing output directory. Use --output-dir ."); + + Common::String inputDir = _inputPaths[0].path.c_str(); + Common::String outputDir = _outputPath.getFullPath().c_str(); + + DiskSet ds = loadDisks(inputDir); + + Common::HashMap images; + Common::HashMap vols; + + Common::Array imgStore; + Common::Array volStore; + + for (Common::HashMap::const_iterator kv = ds.diskPaths.begin(); kv != ds.diskPaths.end(); ++kv) { + int disk = kv->_key; + const Common::String &path = kv->_value; + + AdfImage *img = new AdfImage(path); + imgStore.push_back(img); + images[disk] = img; + + AdfsVolume *vol = new AdfsVolume(*images[disk], disk != 10); + if (!vol->isValid() && disk == 10) { + delete vol; + vols[disk] = nullptr; + warning("Disk 10 present but does not contain a usable !Simon ADFS tree, skipping."); + continue; + } + + volStore.push_back(vol); + vols[disk] = vol; + debug(1, "Loaded disk %d: %s (%u bytes)", disk, path.c_str(), (uint)images[disk]->originalSize()); + } + + ensureDir(this, outputDir); + runInstallScriptFake(this, vols, images, outputDir); + + debug(1, "Done."); + + for (uint di = 0; di < volStore.size(); di++) + delete volStore[di]; + for (uint di = 0; di < imgStore.size(); di++) + delete imgStore[di]; +} + +#ifdef STANDALONE_MAIN +int main(int argc, char *argv[]) { + ExtractSimonAcorn tool(argv[0]); + return tool.run(argc, argv); +} +#endif + diff --git a/engines/agos/extract_simon_acorn.h b/engines/agos/extract_simon_acorn.h new file mode 100644 index 00000000..d2ac1705 --- /dev/null +++ b/engines/agos/extract_simon_acorn.h @@ -0,0 +1,152 @@ +/* ScummVM Tools + * + * ScummVM Tools is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* The ADFS filesystem parsing routines are based on Gerald Holdsworth's + * DiscImageManager: + * https://github.com/geraldholdsworth/DiscImageManager/ + * + * Many thanks to Mike Woodroffe of Adventure Soft for giving + * permission for this tool to include the disk 10 data. + */ + + +#ifndef EXTRACT_SIMON_ACORN_H +#define EXTRACT_SIMON_ACORN_H + +#include "tool.h" +#include "common/scummsys.h" +#include "common/str.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +struct UcmpResult { + bool ok; + Common::String error; + uint32 expectedOut; + uint32 producedOut; + byte innerMethod; + Common::Array out; +}; + +class AdfImage { +public: + explicit AdfImage(const Common::String &path); + + const Common::String &path() const { return _path; } + uint size() const { return _data.size(); } + uint originalSize() const { return _originalSize; } + + Common::Array slice(uint off, uint len) const; + +private: + Common::String _path; + Common::Array _data; + uint _originalSize = 0; +}; + +struct AdfsObject { + Common::String name; + bool isDir; + uint32 loadAddr; + uint32 execAddr; + uint32 length; + uint32 startDiscaddr; + uint32 parentDirDiscaddr; +}; + +struct Disk10OldDirEntry { + bool ok; + uint32 length; + uint32 sector; +}; + +struct HugoNickDiscAddrEntry { + bool ok; + uint32 length; + uint32 discaddr; + byte attr; +}; + +class AdfsVolume { +public: + explicit AdfsVolume(const AdfImage &img, bool requireSimon); + + bool isValid() const { return _valid; } + uint32 rootParentDirDiscaddr() const { return _rootParentDirDiscaddr; } + uint32 simonDirDiscaddr() const { return _simonDirDiscaddr; } + + bool findPath(const Common::String &adfsPath, AdfsObject &outObj) const; + Common::Array listDir(const Common::String &adfsDirPath) const; + Common::Array listDirByDiscaddr(uint32 discaddr) const; + Common::Array readFile(const AdfsObject &f) const; + +private: + struct Frag { + uint32 offset; + uint32 length; + }; + + bool tryInit(bool requireSimon); + bool tryParseDiscRecord(); + Common::Array discaddrToFrags(uint32 addr) const; + Common::StringList splitDotPath(const Common::String &p) const; + + const AdfImage &_img; + + uint32 _secSize = 1024; + uint32 _nzones = 1; + uint32 _bootmap = 0; + uint32 _idlen = 15; + uint32 _zoneSpareBits = 0; + uint32 _bpmb = 128; + uint32 _discSize = 819200; + + uint32 _rootDirDiscaddr = 0xFFFFFFFFu; + uint32 _rootParentDirDiscaddr = 0xFFFFFFFFu; + bool _valid = false; + uint32 _simonDirDiscaddr = 0xFFFFFFFFu; + + static uint16 readUint16LELocal(const byte *p) { + return (uint16)p[0] | ((uint16)p[1] << 8); + } + byte readU8(uint32 off) const; + static bool bitIsSet(byte v, uint32 b) { + return (v & (byte)(1u << (b & 7u))) != 0; + } + uint32 readBits(uint32 baseOff, uint32 startBit, uint32 nbits) const; +}; + +struct DiskSet { + Common::HashMap diskPaths; +}; + +class ExtractSimonAcorn : public Tool { +public: + ExtractSimonAcorn(const std::string &name = "extract_simon_acorn"); + +protected: + void parseExtraArguments() override; + void execute() override; +}; + + +#endif // EXTRACT_SIMON_ACORN_H diff --git a/tools.cpp b/tools.cpp index 497d79fb..dff73ebf 100644 --- a/tools.cpp +++ b/tools.cpp @@ -47,6 +47,7 @@ #endif #include "engines/agos/extract_agos.h" +#include "engines/agos/extract_simon_acorn.h" #include "engines/bladerunner/pack_bladerunner.h" #include "engines/cge/extract_cge.h" #include "engines/cge/pack_cge.h" @@ -90,6 +91,7 @@ Tools::Tools() { #endif _tools.push_back(new ExtractAgos()); + _tools.push_back(new ExtractSimonAcorn()); _tools.push_back(new ExtractAsylum()); _tools.push_back(new PackBladeRunner()); _tools.push_back(new ExtractCge());