Initial commit
This commit is contained in:
426
engines/freescape/unpack.cpp
Normal file
426
engines/freescape/unpack.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
/* 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;
|
||||
unsigned int cur_index = 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 ((unsigned int)(buf - save_buf) >= packed_data_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur_index = (unsigned int)(buf - save_buf);
|
||||
if (cur_index < packed_data_len) {
|
||||
if ((packed_data_len - cur_index) > (*unpacked_data_size - (unpacked_data - save_unp))) {
|
||||
debug("Data left are too large!");
|
||||
}
|
||||
memcpy(unpacked_data, buf, packed_data_len - cur_index);
|
||||
cur_unpacked_data_size += packed_data_len - cur_index;
|
||||
}
|
||||
*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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user