mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
424 lines
12 KiB
C++
424 lines
12 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM 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/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
|
|
This code was modified from https://github.com/w4kfu/unEXEPACK/blob/master/unpack.c
|
|
|
|
*/
|
|
|
|
#include "common/file.h"
|
|
#include "common/memstream.h"
|
|
|
|
#include "freescape/freescape.h"
|
|
|
|
namespace Freescape {
|
|
|
|
#define DOS_SIGNATURE 0x5A4D
|
|
#define EXEPACK_SIGNATURE 0x4252
|
|
|
|
struct memstream {
|
|
unsigned char *buf;
|
|
unsigned int length;
|
|
unsigned int pos;
|
|
};
|
|
|
|
struct dos_header {
|
|
unsigned short e_magic;
|
|
unsigned short e_cblp;
|
|
unsigned short e_cp;
|
|
unsigned short e_crlc;
|
|
unsigned short e_cparhdr;
|
|
unsigned short e_minalloc;
|
|
unsigned short e_maxalloc;
|
|
unsigned short e_ss;
|
|
unsigned short e_sp;
|
|
unsigned short e_csum;
|
|
unsigned short e_ip;
|
|
unsigned short e_cs;
|
|
unsigned short e_lfarlc;
|
|
unsigned short e_ovno;
|
|
};
|
|
|
|
struct exepack_header {
|
|
unsigned short real_ip;
|
|
unsigned short real_cs;
|
|
unsigned short mem_start;
|
|
unsigned short exepack_size;
|
|
unsigned short real_sp;
|
|
unsigned short real_ss;
|
|
unsigned short dest_len;
|
|
unsigned short skip_len;
|
|
unsigned short signature;
|
|
};
|
|
|
|
void reverse(unsigned char *s, size_t length);
|
|
void unpack_data(unsigned char *unpacked_data, unsigned char *buf, unsigned int *unpacked_data_size, unsigned int packed_data_len);
|
|
Common::MemoryReadStream *unpack(struct memstream *ms);
|
|
unsigned char *create_reloc_table(struct memstream *ms, struct dos_header *dh, struct exepack_header *eh, unsigned int *reloc_table_size);
|
|
Common::MemoryReadStream *writeExe(struct dos_header *dh, unsigned char *unpacked_data, unsigned int unpacked_data_size, unsigned char *reloc, size_t reloc_size, size_t padding);
|
|
Common::MemoryReadStream *craftexec(struct dos_header *dh, struct exepack_header *eh, unsigned char *unpacked_data, unsigned int unpacked_data_size, unsigned char *reloc, unsigned int reloc_size);
|
|
|
|
// Utils
|
|
int test_dos_header(struct memstream *ms);
|
|
void msopen(Common::File &file, struct memstream *ms);
|
|
unsigned int msread(struct memstream *ms, void *buf, unsigned int count);
|
|
int mscanread(struct memstream *ms, unsigned int count);
|
|
unsigned int msgetavailable(struct memstream *ms);
|
|
void msseek(struct memstream *ms, unsigned int offset);
|
|
void msclose(struct memstream *ms);
|
|
void *memmem(void *l, size_t l_len, const void *s, size_t s_len);
|
|
|
|
void reverse(unsigned char *s, size_t length) {
|
|
size_t i, j;
|
|
unsigned char c;
|
|
|
|
if (length == 0x00) {
|
|
return;
|
|
}
|
|
for (i = 0, j = length - 1; i < j; i++, j--) {
|
|
c = s[i];
|
|
s[i] = s[j];
|
|
s[j] = c;
|
|
}
|
|
}
|
|
|
|
/* buf is already reversed, because EXEPACK use backward processing */
|
|
void unpack_data(unsigned char *unpacked_data, unsigned char *buf, unsigned int *unpacked_data_size, unsigned int packed_data_len) {
|
|
unsigned char opcode;
|
|
unsigned short count;
|
|
unsigned char fillbyte;
|
|
unsigned char *save_buf = NULL;
|
|
unsigned char *save_unp = NULL;
|
|
unsigned int cur_unpacked_data_size = 0x00;
|
|
|
|
save_buf = buf;
|
|
save_unp = unpacked_data;
|
|
while (*buf == 0xFF) {
|
|
buf++;
|
|
}
|
|
while (1) {
|
|
opcode = *buf++;
|
|
count = *(buf) * 0x100 + *(buf + 1);
|
|
buf += 2;
|
|
if ((opcode & 0xFE) == 0xB0) {
|
|
fillbyte = *buf++;
|
|
if ((cur_unpacked_data_size + count) > *unpacked_data_size) {
|
|
debug("overflow");
|
|
}
|
|
memset(unpacked_data, fillbyte, count);
|
|
unpacked_data += count;
|
|
cur_unpacked_data_size += count;
|
|
} else if ((opcode & 0xFE) == 0xB2) {
|
|
if ((cur_unpacked_data_size + count) > *unpacked_data_size) {
|
|
debug("overflow");
|
|
}
|
|
memcpy(unpacked_data, buf, count);
|
|
unpacked_data += count;
|
|
cur_unpacked_data_size += count;
|
|
buf += count;
|
|
} else {
|
|
debug("unknown opcode");
|
|
}
|
|
if ((opcode & 1) == 1) {
|
|
break;
|
|
}
|
|
if (buf - save_buf >= packed_data_len) {
|
|
break;
|
|
}
|
|
}
|
|
if (buf - save_buf < packed_data_len) {
|
|
if ((packed_data_len - (buf - save_buf)) > (*unpacked_data_size - (unpacked_data - save_unp))) {
|
|
debug("Data left are too large!");
|
|
}
|
|
memcpy(unpacked_data, buf, packed_data_len - (buf - save_buf));
|
|
cur_unpacked_data_size += packed_data_len - (buf - save_buf);
|
|
}
|
|
*unpacked_data_size = cur_unpacked_data_size;
|
|
}
|
|
|
|
unsigned char *create_reloc_table(struct memstream *ms, struct dos_header *dh, struct exepack_header *eh, unsigned int *reloc_table_size) {
|
|
unsigned int exepack_offset = 0x00;
|
|
unsigned int reloc_length;
|
|
int nb_reloc;
|
|
unsigned char *buf_reloc = NULL;
|
|
unsigned char *reloc = NULL;
|
|
int i, j;
|
|
unsigned short count = 0x00;
|
|
unsigned short entry;
|
|
unsigned int reloc_position = 0x00;
|
|
|
|
exepack_offset = (dh->e_cparhdr + dh->e_cs) * 16;
|
|
msseek(ms, exepack_offset);
|
|
reloc = (unsigned char*)memmem(ms->buf + exepack_offset, msgetavailable(ms), "Packed file is corrupt", strlen("Packed file is corrupt"));
|
|
if (!reloc) {
|
|
debug("Cannot find string \"Packed file is corrupt\", is it really EXEPACK ?");
|
|
}
|
|
|
|
reloc_length = (unsigned int)(eh->exepack_size - ((reloc - (ms->buf + exepack_offset)) & 0xFFFFFFFF) + strlen("Packed file is corrupt"));
|
|
nb_reloc = (reloc_length - 16 * sizeof(unsigned short)) / 2;
|
|
*reloc_table_size = nb_reloc * 2 * sizeof(unsigned short);
|
|
buf_reloc = (unsigned char*)malloc(sizeof(char) * *reloc_table_size);
|
|
assert(buf_reloc);
|
|
reloc += strlen("Packed file is corrupt");
|
|
msseek(ms, (reloc - ms->buf) & 0xFFFFFFFF);
|
|
for (i = 0; i < 16; i++) {
|
|
if (msread(ms, &count, sizeof(unsigned short)) != sizeof(unsigned short)) {
|
|
debug("msread failed");
|
|
}
|
|
for (j = 0; j < count; j++) {
|
|
if (msread(ms, &entry, sizeof(unsigned short)) != sizeof(unsigned short)) {
|
|
debug("msread failed");
|
|
}
|
|
if (reloc_position >= *reloc_table_size) {
|
|
debug("overflow");
|
|
}
|
|
*(unsigned short*)(buf_reloc + reloc_position) = entry;
|
|
reloc_position += 2;
|
|
if (reloc_position >= *reloc_table_size) {
|
|
debug("overflow");
|
|
}
|
|
*(unsigned short*)(buf_reloc + reloc_position) = (i * 0x1000) & 0xFFFF;
|
|
reloc_position += 2;
|
|
}
|
|
}
|
|
*reloc_table_size = reloc_position;
|
|
return buf_reloc;
|
|
}
|
|
|
|
Common::MemoryReadStream *writeExe(struct dos_header *dh, unsigned char *unpacked_data, unsigned int unpacked_data_size, unsigned char *reloc, size_t reloc_size, size_t padding) {
|
|
Common::MemoryWriteStreamDynamic buf(DisposeAfterUse::NO);
|
|
|
|
buf.write(dh, sizeof(struct dos_header));
|
|
buf.write(reloc, reloc_size);
|
|
for (size_t i = 0; i < padding; i++) {
|
|
buf.write("\x00", 1);
|
|
}
|
|
buf.write(unpacked_data, unpacked_data_size);
|
|
return (new Common::MemoryReadStream(buf.getData(), buf.size()));
|
|
}
|
|
|
|
Common::MemoryReadStream *craftexec(struct dos_header *dh, struct exepack_header *eh, unsigned char *unpacked_data, unsigned int unpacked_data_size, unsigned char *reloc, unsigned int reloc_size) {
|
|
struct dos_header dhead;
|
|
int header_size;
|
|
int total_length;
|
|
int padding_length;
|
|
|
|
memset(&dhead, 0, sizeof(struct dos_header));
|
|
header_size = sizeof(struct dos_header) + reloc_size;
|
|
dhead.e_magic = DOS_SIGNATURE;
|
|
dhead.e_cparhdr = (header_size / 16) & 0xFFFF;
|
|
dhead.e_cparhdr = (dhead.e_cparhdr / 32 + 1) * 32;
|
|
padding_length = dhead.e_cparhdr * 16 - header_size;
|
|
total_length = header_size + padding_length + unpacked_data_size;
|
|
dhead.e_ss = eh->real_ss;
|
|
dhead.e_sp = eh->real_sp;
|
|
dhead.e_ip = eh->real_ip;
|
|
dhead.e_cs = eh->real_cs;
|
|
dhead.e_minalloc = dh->e_minalloc;
|
|
dhead.e_maxalloc = 0xFFFF;
|
|
dhead.e_lfarlc = sizeof(struct dos_header);
|
|
dhead.e_crlc = (reloc_size / (2 * sizeof(unsigned short))) & 0xFFFF;
|
|
dhead.e_cblp = total_length % 512;
|
|
dhead.e_cp = (total_length / 512 + 1) & 0xFFFF;
|
|
//print_dos_header(&dhead);
|
|
return writeExe(&dhead, unpacked_data, unpacked_data_size, reloc, reloc_size, padding_length);
|
|
}
|
|
|
|
Common::MemoryReadStream *unpack(struct memstream *ms) {
|
|
struct dos_header dh;
|
|
struct exepack_header eh;
|
|
unsigned int exepack_offset = 0x00;
|
|
unsigned char *unpacked_data = NULL;
|
|
unsigned int unpacked_data_size = 0x00;
|
|
unsigned int packed_data_start;
|
|
unsigned int packed_data_end;
|
|
unsigned int packed_data_len;
|
|
unsigned int reloc_size;
|
|
unsigned char *reloc = NULL;
|
|
|
|
if (msread(ms, &dh, sizeof(struct dos_header)) != sizeof(struct dos_header)) {
|
|
return nullptr;
|
|
}
|
|
//print_dos_header(&dh);
|
|
exepack_offset = (dh.e_cparhdr + dh.e_cs) * 16;
|
|
msseek(ms, exepack_offset);
|
|
if (msread(ms, &eh, sizeof(struct exepack_header)) != sizeof(struct exepack_header)) {
|
|
return nullptr;
|
|
}
|
|
//print_exepack_header(&eh);
|
|
if ((eh.signature != EXEPACK_SIGNATURE && eh.skip_len != EXEPACK_SIGNATURE) || eh.exepack_size == 0x00) {
|
|
debug("This is not a valid EXEPACK executable");
|
|
return nullptr;
|
|
}
|
|
debug("Header exepack = %X\n", exepack_offset);
|
|
//print_exepack_header(&eh);
|
|
unpacked_data_size = eh.dest_len * 16;
|
|
unpacked_data = (unsigned char*)malloc(sizeof(char) * unpacked_data_size);
|
|
assert(unpacked_data);
|
|
memset(unpacked_data, 0x00, sizeof(char) * unpacked_data_size);
|
|
packed_data_start = dh.e_cparhdr * 16;
|
|
packed_data_end = exepack_offset;
|
|
packed_data_len = packed_data_end - packed_data_start;
|
|
msseek(ms, packed_data_start);
|
|
if (mscanread(ms, packed_data_len) == 0x00) {
|
|
free(unpacked_data);
|
|
return nullptr;
|
|
}
|
|
reverse(ms->buf + packed_data_start, packed_data_len);
|
|
unpack_data(unpacked_data, ms->buf + packed_data_start, &unpacked_data_size, packed_data_len);
|
|
reverse(unpacked_data, unpacked_data_size);
|
|
reloc = create_reloc_table(ms, &dh, &eh, &reloc_size);
|
|
Common::MemoryReadStream *ret = craftexec(&dh, &eh, unpacked_data, unpacked_data_size, reloc, reloc_size);
|
|
free(reloc);
|
|
free(unpacked_data);
|
|
return ret;
|
|
}
|
|
|
|
void *memmem(void *l, size_t l_len, const void *s, size_t s_len) {
|
|
char *cur, *last;
|
|
char *cl = (char *)l;
|
|
const char *cs = (const char *)s;
|
|
|
|
if (l_len == 0 || s_len == 0) {
|
|
return NULL;
|
|
}
|
|
if (l_len < s_len) {
|
|
return NULL;
|
|
}
|
|
if (s_len == 1) {
|
|
return (void *)memchr(l, (int)*cs, l_len);
|
|
}
|
|
last = cl + l_len - s_len;
|
|
for (cur = (char *)cl; cur <= last; cur++) {
|
|
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) {
|
|
return cur;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void msopen(Common::File &file, struct memstream *ms) {
|
|
assert(ms);
|
|
|
|
ms->buf = (unsigned char*)malloc(sizeof(char) * file.size());
|
|
assert(ms->buf);
|
|
|
|
assert(file.read(ms->buf, file.size()) == file.size());
|
|
ms->pos = 0x00;
|
|
ms->length = file.size();
|
|
}
|
|
|
|
unsigned int msread(struct memstream *ms, void *buf, unsigned int count) {
|
|
unsigned int length;
|
|
|
|
if (buf == NULL) {
|
|
return 0;
|
|
}
|
|
if (ms->pos > ms->length) {
|
|
debug("invalid read");
|
|
}
|
|
if (count < (ms->length - ms->pos)) {
|
|
length = count;
|
|
} else {
|
|
length = ms->length - ms->pos;
|
|
}
|
|
if (length > 0) {
|
|
memcpy(buf, ms->buf + ms->pos, length);
|
|
}
|
|
ms->pos += length;
|
|
return length;
|
|
}
|
|
|
|
int mscanread(struct memstream *ms, unsigned int count) {
|
|
if (ms->pos > ms->length) {
|
|
return 0;
|
|
}
|
|
if (count > (ms->length - ms->pos)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
unsigned int msgetavailable(struct memstream *ms) {
|
|
if (ms->pos > ms->length) {
|
|
return 0;
|
|
}
|
|
return ms->length - ms->pos;
|
|
}
|
|
|
|
void msseek(struct memstream *ms, unsigned int offset) {
|
|
if (offset > ms->length) {
|
|
debug("invalid seek : 0x%X", offset);
|
|
}
|
|
ms->pos = offset;
|
|
}
|
|
|
|
void msclose(struct memstream *ms) {
|
|
if (ms != NULL) {
|
|
if (ms->buf != NULL) {
|
|
free(ms->buf);
|
|
ms->buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
int test_dos_header(struct memstream *ms) {
|
|
struct dos_header dh;
|
|
|
|
if (ms == NULL) {
|
|
return 0;
|
|
}
|
|
if (msread(ms, &dh, sizeof(struct dos_header)) != sizeof(struct dos_header)) {
|
|
return 0;
|
|
}
|
|
msseek(ms, 0x00);
|
|
if (dh.e_magic != DOS_SIGNATURE) {
|
|
return 0;
|
|
}
|
|
/* at least one page */
|
|
if (dh.e_cp == 0) {
|
|
return 0;
|
|
}
|
|
/* last page must not hold 0 bytes */
|
|
if (dh.e_cblp == 0) {
|
|
return 0;
|
|
}
|
|
/* not even number of paragraphs */
|
|
if (dh.e_cparhdr % 2 != 0) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
Common::MemoryReadStream *unpackEXE(Common::File &file) {
|
|
struct memstream ms;
|
|
msopen(file, &ms);
|
|
if (test_dos_header(&ms) == 0) {
|
|
msclose(&ms);
|
|
return nullptr;
|
|
}
|
|
Common::MemoryReadStream *ret = unpack(&ms);
|
|
msclose(&ms);
|
|
return ret;
|
|
}
|
|
|
|
}
|