mirror of
https://github.com/scummvm/scummvm-tools.git
synced 2026-05-21 05:40:44 +00:00
1594 lines
54 KiB
C++
1594 lines
54 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/* 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 <errno.h>
|
|
#ifdef WIN32
|
|
#include <direct.h>
|
|
#else
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#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<byte> &buf);
|
|
static bool readFileAnyDisk(const Common::HashMap<int, AdfsVolume *> &vols, const Common::String &adfsFilePath, uint32 wantLen, Common::Array<byte> &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<byte> 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<byte> 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<byte> &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<byte> &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<byte> &data) {
|
|
if (data.empty() || isAllZeros(data)) {
|
|
return false;
|
|
}
|
|
if (pathExists(p)) {
|
|
Common::Array<byte> 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<byte> &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<byte> &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<byte> 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<byte> AdfImage::slice(uint off, uint len) const {
|
|
if (len == 0) return Common::Array<byte>();
|
|
Common::Array<byte> 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<byte> &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<byte> 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<Disk10OldDirEntry> disk10BrutefindLeafEntries(const AdfImage &img, const Common::String &leafName) {
|
|
Common::Array<Disk10OldDirEntry> 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<byte> 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<byte> 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<AdfsObject> 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<AdfsObject> 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<byte> AdfsVolume::readFile(const AdfsObject &f) const {
|
|
if (f.isDir) { error("readFile called on dir"); return Common::Array<byte>(); }
|
|
|
|
Common::Array<AdfsVolume::Frag> 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<byte>();
|
|
}
|
|
|
|
Common::Array<byte> 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<byte> 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<byte> 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<byte> 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::Frag> AdfsVolume::discaddrToFrags(uint32 addr) const {
|
|
const uint32 drSize = 0x40;
|
|
const uint32 header = 4;
|
|
|
|
Common::Array<AdfsVolume::Frag> 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<AdfsObject> AdfsVolume::listDirByDiscaddr(uint32 dirDiscaddr) const {
|
|
Common::Array<AdfsVolume::Frag> 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<AdfsObject>();
|
|
if (fr[0].offset >= _img.size()) return Common::Array<AdfsObject>();
|
|
|
|
const size_t entrySize = 0x1A;
|
|
const size_t entryBase = 5;
|
|
|
|
Common::Array<AdfsObject> out;
|
|
out.reserve(128);
|
|
|
|
Common::HashMap<Common::String, bool> seenLower;
|
|
|
|
auto parseBlock = [&](const Common::Array<byte> &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<byte> blk0 = _img.slice(fr[0].offset, 0x800);
|
|
if (blk0.size() < 0x800) return Common::Array<AdfsObject>();
|
|
|
|
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<uint64>(fr[0].offset) + static_cast<uint64>(b) * 0x800ull;
|
|
if (off + 0x800ull > _img.size()) break;
|
|
|
|
Common::Array<byte> blkN = _img.slice(static_cast<size_t>(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<AdfsObject> AdfsVolume::listDir(const Common::String &adfsDirPath) const {
|
|
if (adfsDirPath.empty()) {
|
|
return listDirByDiscaddr(_rootParentDirDiscaddr);
|
|
}
|
|
AdfsObject dirObj;
|
|
if (!findPath(adfsDirPath, dirObj)) return Common::Array<AdfsObject>();
|
|
if (!dirObj.isDir) return Common::Array<AdfsObject>();
|
|
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<byte> &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<int, AdfsVolume *> &vols, const Common::String &adfsFilePath, uint32 wantLen, Common::Array<byte> &outData) {
|
|
for (Common::HashMap<int, AdfsVolume *>::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<byte> 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<int, AdfsVolume *> &vols, const AdfsVolume &vol, const Common::String &adfsDirPath, const Common::String &pattern, const Common::String &outDir, const Common::String &relOutDir, int diskForLog) {
|
|
Common::Array<AdfsObject> 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<byte> data = vol.readFile(e);
|
|
|
|
if (data.size() != (size_t)e.length || looksLikeAdfsDirBlock(data)) {
|
|
Common::Array<byte> 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<int, AdfsVolume *> &vols, const AdfsVolume &vol, const Common::String &adfsDirPath, const Common::String &pattern, const Common::String &outDir, const Common::String &relOutDir, int diskForLog) {
|
|
Common::Array<AdfsObject> 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<byte> data = vol.readFile(e);
|
|
|
|
if (data.size() != (size_t)e.length || looksLikeAdfsDirBlock(data)) {
|
|
Common::Array<byte> 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<byte> out;
|
|
|
|
const size_t tryOffs[] = { 8, 4, 0 };
|
|
for (size_t off : tryOffs) {
|
|
if (data.size() <= off) continue;
|
|
Common::Array<byte> 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<int, AdfsVolume *> &vols, const Common::HashMap<int, AdfImage *> &images, const Common::String &outRoot) {
|
|
createInstallDirs(tool, outRoot);
|
|
|
|
auto getVol = [&](int disk) -> const AdfsVolume& {
|
|
Common::HashMap<int, AdfsVolume*>::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<int, AdfImage*>::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<byte> data;
|
|
|
|
{
|
|
HugoNickDiscAddrEntry he = hugonickBrutefindLeafDiscaddr(img, leaf);
|
|
if (he.ok) {
|
|
Common::HashMap<int, AdfsVolume*>::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<byte> 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<byte> 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<int, AdfImage*>::const_iterator itImg = images.find(10);
|
|
if (itImg != images.end()) img10 = itImg->_value;
|
|
}
|
|
|
|
Common::HashMap<int, AdfsVolume*>::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<byte> 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<Disk10OldDirEntry> 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<byte> 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<byte> cur = readFileBytes(p);
|
|
if (!vecEqualsBlob(cur, kFIX_0621, kFIX_0621_LEN)) {
|
|
Common::Array<byte> 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<byte> cur = readFileBytes(p);
|
|
if (!vecEqualsBlob(cur, kFIX_0622, kFIX_0622_LEN)) {
|
|
Common::Array<byte> 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 <inputdir> --output-dir <outputdir>\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 <outputdir>.");
|
|
|
|
Common::String inputDir = _inputPaths[0].path.c_str();
|
|
Common::String outputDir = _outputPath.getFullPath().c_str();
|
|
|
|
DiskSet ds = loadDisks(inputDir);
|
|
|
|
Common::HashMap<int, AdfImage *> images;
|
|
Common::HashMap<int, AdfsVolume *> vols;
|
|
|
|
Common::Array<AdfImage *> imgStore;
|
|
Common::Array<AdfsVolume *> volStore;
|
|
|
|
for (Common::HashMap<int, Common::String>::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
|
|
|