mirror of
https://github.com/autc04/Retro68.git
synced 2024-06-01 15:41:39 +00:00
506 lines
16 KiB
C++
506 lines
16 KiB
C++
/* Copyright (C) 2021 Free Software Foundation, Inc.
|
|
Contributed by Oracle.
|
|
|
|
This file is part of GNU Binutils.
|
|
|
|
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, 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, write to the Free Software
|
|
Foundation, 51 Franklin Street - Fifth Floor, Boston,
|
|
MA 02110-1301, USA. */
|
|
|
|
#include "config.h"
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "zlib.h"
|
|
#include "util.h"
|
|
#include "DbeJarFile.h"
|
|
#include "Data_window.h"
|
|
#include "vec.h"
|
|
|
|
static uint32_t
|
|
get_u1 (unsigned char *b)
|
|
{
|
|
return (uint32_t) ((b)[0]);
|
|
}
|
|
|
|
static uint32_t
|
|
get_u2 (unsigned char *b)
|
|
{
|
|
return (get_u1 (b + 1) << 8) | get_u1 (b);
|
|
}
|
|
|
|
static uint32_t
|
|
get_u4 (unsigned char *b)
|
|
{
|
|
return (get_u2 (b + 2) << 16) | get_u2 (b);
|
|
}
|
|
|
|
static uint64_t
|
|
get_u8 (unsigned char *b)
|
|
{
|
|
return (((uint64_t) get_u4 (b + 4)) << 32) | get_u4 (b);
|
|
}
|
|
|
|
enum
|
|
{
|
|
END_CENT_DIR_SIZE = 22,
|
|
LOC_FILE_HEADER_SIZE = 30,
|
|
CENT_FILE_HEADER_SIZE = 46,
|
|
ZIP64_LOCATOR_SIZE = 20,
|
|
ZIP64_CENT_DIR_SIZE = 56,
|
|
ZIP_BUF_SIZE = 65536
|
|
};
|
|
|
|
struct EndCentDir
|
|
{
|
|
uint64_t count;
|
|
uint64_t size;
|
|
uint64_t offset;
|
|
};
|
|
|
|
class ZipEntry
|
|
{
|
|
public:
|
|
|
|
ZipEntry ()
|
|
{
|
|
name = NULL;
|
|
data_offset = 0;
|
|
}
|
|
|
|
~ZipEntry ()
|
|
{
|
|
free (name);
|
|
}
|
|
|
|
int
|
|
compare (ZipEntry *ze)
|
|
{
|
|
return dbe_strcmp (name, ze->name);
|
|
}
|
|
|
|
char *name; // entry name
|
|
int time; // modification time
|
|
int64_t size; // size of uncompressed data
|
|
int64_t csize; // size of compressed data (zero if uncompressed)
|
|
uint32_t compressionMethod;
|
|
int64_t offset; // offset of LOC header
|
|
int64_t data_offset;
|
|
};
|
|
|
|
static int
|
|
cmp_names (const void *a, const void *b)
|
|
{
|
|
ZipEntry *e1 = *((ZipEntry **) a);
|
|
ZipEntry *e2 = *((ZipEntry **) b);
|
|
return e1->compare (e2);
|
|
}
|
|
|
|
template<> void Vector<ZipEntry *>::dump (const char *msg)
|
|
{
|
|
Dprintf (1, NTXT ("Vector<ZipEntry *> %s [%lld]\n"), msg ? msg : NTXT (""), (long long) size ());
|
|
for (long i = 0, sz = size (); i < sz; i++)
|
|
{
|
|
ZipEntry *ze = get (i);
|
|
Dprintf (1, NTXT (" %lld offset:%lld (0x%llx) size: %lld --> %lld %s\n"),
|
|
(long long) i, (long long) ze->offset, (long long) ze->offset,
|
|
(long long) ze->csize, (long long) ze->size, STR (ze->name));
|
|
}
|
|
}
|
|
|
|
DbeJarFile::DbeJarFile (const char *jarName)
|
|
{
|
|
name = strdup (jarName);
|
|
fnames = NULL;
|
|
dwin = new Data_window (name);
|
|
get_entries ();
|
|
}
|
|
|
|
DbeJarFile::~DbeJarFile ()
|
|
{
|
|
free (name);
|
|
delete fnames;
|
|
}
|
|
|
|
void
|
|
DbeJarFile::get_entries ()
|
|
{
|
|
Dprintf (DUMP_JAR_FILE, NTXT ("\nArchive: %s\n"), STR (name));
|
|
if (dwin->not_opened ())
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("Cannot open file `%s'"), name);
|
|
return;
|
|
}
|
|
struct EndCentDir endCentDir;
|
|
if (get_EndCentDir (&endCentDir) == 0)
|
|
return;
|
|
|
|
if (endCentDir.count == 0)
|
|
{
|
|
append_msg (CMSG_WARN, GTXT ("No files in %s"), name);
|
|
return;
|
|
}
|
|
unsigned char *b = (unsigned char *) dwin->bind (endCentDir.offset, endCentDir.size);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: cannot read the central directory record"), name);
|
|
return;
|
|
}
|
|
|
|
fnames = new Vector<ZipEntry*>(endCentDir.count);
|
|
for (uint64_t i = 0, offset = endCentDir.offset, last = endCentDir.offset + endCentDir.size; i < endCentDir.count; i++)
|
|
{
|
|
if ((last - offset) < CENT_FILE_HEADER_SIZE)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: cannot read the central file header (%lld (from %lld), offset=0x%016llx last=0x%016llx"),
|
|
name, (long long) i, (long long) endCentDir.count, (long long) offset, (long long) last);
|
|
break;
|
|
}
|
|
b = (unsigned char *) dwin->bind (offset, CENT_FILE_HEADER_SIZE);
|
|
// Central file header
|
|
// Offset Bytes Description
|
|
// 0 4 central file header signature = 0x02014b50
|
|
// 4 2 version made by
|
|
// 6 2 version needed to extract
|
|
// 8 2 general purpose bit flag
|
|
// 10 2 compression method
|
|
// 12 2 last mod file time
|
|
// 14 2 last mod file date
|
|
// 16 4 crc-32
|
|
// 20 4 compressed size
|
|
// 24 4 uncompressed size
|
|
// 28 2 file name length
|
|
// 30 2 extra field length
|
|
// 32 2 file comment length
|
|
// 34 2 disk number start
|
|
// 36 2 internal file attributes
|
|
// 38 4 external file attributes
|
|
// 42 4 relative offset of local header
|
|
// 46 file name (variable size)
|
|
// extra field (variable size)
|
|
// file comment (variable size)
|
|
uint32_t signature = get_u4 (b);
|
|
if (signature != 0x02014b50)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: wrong header signature (%lld (total %lld), offset=0x%016llx last=0x%016llx"),
|
|
name, (long long) i, (long long) endCentDir.count, (long long) offset, (long long) last);
|
|
break;
|
|
}
|
|
ZipEntry *ze = new ZipEntry ();
|
|
fnames->append (ze);
|
|
uint32_t name_len = get_u2 (b + 28);
|
|
uint32_t extra_len = get_u2 (b + 30);
|
|
uint32_t comment_len = get_u2 (b + 32);
|
|
ze->compressionMethod = get_u2 (b + 10);
|
|
ze->csize = get_u4 (b + 20);
|
|
ze->size = get_u4 (b + 24);
|
|
ze->offset = get_u4 (b + 42);
|
|
char *nm = (char *) dwin->bind (offset + 46, name_len);
|
|
if (nm)
|
|
{
|
|
ze->name = (char *) malloc (name_len + 1);
|
|
strncpy (ze->name, nm, name_len);
|
|
ze->name[name_len] = 0;
|
|
}
|
|
offset += CENT_FILE_HEADER_SIZE + name_len + extra_len + comment_len;
|
|
}
|
|
fnames->sort (cmp_names);
|
|
if (DUMP_JAR_FILE)
|
|
fnames->dump (get_basename (name));
|
|
}
|
|
|
|
int
|
|
DbeJarFile::get_entry (const char *fname)
|
|
{
|
|
if (fnames == NULL)
|
|
return -1;
|
|
ZipEntry zipEntry, *ze = &zipEntry;
|
|
ze->name = (char *) fname;
|
|
int ind = fnames->bisearch (0, -1, &ze, cmp_names);
|
|
ze->name = NULL;
|
|
return ind;
|
|
}
|
|
|
|
long long
|
|
DbeJarFile::copy (char *toFileNname, int fromEntryNum)
|
|
{
|
|
if (fromEntryNum < 0 || fromEntryNum >= VecSize (fnames))
|
|
return -1;
|
|
ZipEntry *ze = fnames->get (fromEntryNum);
|
|
if (ze->data_offset == 0)
|
|
{
|
|
// Local file header
|
|
// Offset Bytes Description
|
|
// 0 4 local file header signature = 0x04034b50
|
|
// 4 2 version needed to extract
|
|
// 6 2 general purpose bit flag
|
|
// 8 2 compression method
|
|
// 10 2 last mod file time
|
|
// 12 2 last mod file date
|
|
// 14 4 crc-32
|
|
// 18 4 compressed size
|
|
// 22 4 uncompressed size
|
|
// 26 2 file name length
|
|
// 28 2 extra field length
|
|
// 30 2 file name (variable size)
|
|
// extra field (variable size)
|
|
unsigned char *b = (unsigned char *) dwin->bind (ze->offset, LOC_FILE_HEADER_SIZE);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: Cannot read a local file header (%s offset=0x%lld"),
|
|
name, STR (ze->name), (long long) ze->offset);
|
|
return -1;
|
|
}
|
|
uint32_t signature = get_u4 (b);
|
|
if (signature != 0x04034b50)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: wrong local header signature ('%s' offset=%lld (0x%llx)"),
|
|
name, STR (ze->name), (long long) ze->offset,
|
|
(long long) ze->offset);
|
|
return -1;
|
|
}
|
|
ze->data_offset = ze->offset + LOC_FILE_HEADER_SIZE + get_u2 (b + 26) + get_u2 (b + 28);
|
|
}
|
|
|
|
if (ze->compressionMethod == 0)
|
|
{
|
|
int fd = open (toFileNname, O_CREAT | O_WRONLY | O_LARGEFILE, 0644);
|
|
if (fd == -1)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("Cannot create file %s (%s)"), toFileNname, STR (strerror (errno)));
|
|
return -1;
|
|
}
|
|
long long len = dwin->copy_to_file (fd, ze->data_offset, ze->size);
|
|
close (fd);
|
|
if (len != ze->size)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: Cannot write %lld bytes (only %lld)"),
|
|
toFileNname, (long long) ze->size, (long long) len);
|
|
unlink (toFileNname);
|
|
return -1;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
unsigned char *b = (unsigned char *) dwin->bind (ze->data_offset, ze->csize);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: Cannot extract file %s (offset=0x%lld csize=%lld)"),
|
|
name, STR (ze->name), (long long) ze->offset,
|
|
(long long) ze->csize);
|
|
return -1;
|
|
}
|
|
z_stream strm;
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
strm.next_in = Z_NULL;
|
|
strm.avail_in = 0;
|
|
if (inflateInit2 (&strm, -MAX_WBITS) != Z_OK)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: inflateInit2 failed (%s)"), STR (ze->name), STR (strm.msg));
|
|
return -1;
|
|
}
|
|
strm.avail_in = ze->csize;
|
|
strm.next_in = b;
|
|
int retval = ze->size;
|
|
unsigned char *buf = (unsigned char *) malloc (ze->size);
|
|
for (;;)
|
|
{
|
|
strm.next_out = buf;
|
|
strm.avail_out = ze->size;
|
|
int ret = inflate (&strm, Z_SYNC_FLUSH);
|
|
if ((ret == Z_NEED_DICT) || (ret == Z_DATA_ERROR) || (ret == Z_MEM_ERROR) || (ret == Z_STREAM_ERROR))
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: inflate('%s') error %d (%s)"), name, STR (ze->name), ret, STR (strm.msg));
|
|
retval = -1;
|
|
break;
|
|
}
|
|
if (strm.avail_out != 0)
|
|
break;
|
|
}
|
|
inflateEnd (&strm);
|
|
if (retval != -1)
|
|
{
|
|
int fd = open (toFileNname, O_CREAT | O_WRONLY | O_LARGEFILE, 0644);
|
|
if (fd == -1)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("Cannot create file %s (%s)"), toFileNname, STR (strerror (errno)));
|
|
retval = -1;
|
|
}
|
|
else
|
|
{
|
|
long long len = write (fd, buf, ze->size);
|
|
if (len != ze->size)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: Cannot write %lld bytes (only %lld)"),
|
|
toFileNname, (long long) strm.avail_out, (long long) len);
|
|
retval = -1;
|
|
}
|
|
close (fd);
|
|
}
|
|
}
|
|
free (buf);
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
DbeJarFile::get_EndCentDir (struct EndCentDir *endCentDir)
|
|
{
|
|
int64_t fsize = dwin->get_fsize ();
|
|
int64_t sz = (fsize < ZIP_BUF_SIZE) ? fsize : ZIP_BUF_SIZE;
|
|
|
|
// Find the end of central directory record:
|
|
unsigned char *b = (unsigned char *) dwin->bind (fsize - sz, sz);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: cannot find the central directory record (fsize=%lld)"),
|
|
name, (long long) fsize);
|
|
return 0;
|
|
}
|
|
|
|
// End of central directory record:
|
|
// Offset Bytes Description
|
|
// 0 4 end of central directory signature = 0x06054b50
|
|
// 4 2 number of this disk
|
|
// 6 2 disk where central directory starts
|
|
// 8 2 number of central directory records on this disk
|
|
// 10 2 total number of central directory records
|
|
// 12 4 size of central directory(bytes)
|
|
// 16 4 offset of start of central directory, relative to start of archive
|
|
// 20 2 comment length(n)
|
|
// 22 n comment
|
|
|
|
endCentDir->count = 0;
|
|
endCentDir->size = 0;
|
|
endCentDir->offset = 0;
|
|
int64_t ecdrOffset = fsize;
|
|
for (int64_t i = END_CENT_DIR_SIZE; i < sz; i++)
|
|
{
|
|
b = (unsigned char *) dwin->bind (fsize - i, END_CENT_DIR_SIZE);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: read failed (offset:0x%llx bytes:%lld"),
|
|
name, (long long) (fsize - i), (long long) END_CENT_DIR_SIZE);
|
|
break;
|
|
}
|
|
uint32_t signature = get_u4 (b);
|
|
if (signature == 0x06054b50)
|
|
{
|
|
int64_t len_comment = get_u2 (b + 20);
|
|
if (i != (len_comment + END_CENT_DIR_SIZE))
|
|
continue;
|
|
ecdrOffset = fsize - i;
|
|
endCentDir->count = get_u2 (b + 10);
|
|
endCentDir->size = get_u4 (b + 12);
|
|
endCentDir->offset = get_u4 (b + 16);
|
|
Dprintf (DUMP_JAR_FILE,
|
|
" Zip archive file size: %10lld (0x%016llx)\n"
|
|
" end-cent-dir record offset: %10lld (0x%016llx)\n"
|
|
" cent-dir offset: %10lld (0x%016llx)\n"
|
|
" cent-dir size: %10lld (0x%016llx)\n"
|
|
" cent-dir entries: %10lld\n",
|
|
(long long) fsize, (long long) fsize,
|
|
(long long) ecdrOffset, (long long) ecdrOffset,
|
|
(long long) endCentDir->offset, (long long) endCentDir->offset,
|
|
(long long) endCentDir->size, (long long) endCentDir->size,
|
|
(long long) endCentDir->count);
|
|
break;
|
|
}
|
|
}
|
|
if (ecdrOffset == fsize)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: cannot find the central directory record"), name);
|
|
return 0;
|
|
}
|
|
if (endCentDir->count == 0xffff || endCentDir->offset == 0xffffffff
|
|
|| endCentDir->size == 0xffffffff)
|
|
{
|
|
// Zip64 format:
|
|
// Zip64 end of central directory record
|
|
// Zip64 end of central directory locator ( Can be absent )
|
|
// End of central directory record
|
|
b = (unsigned char *) dwin->bind (ecdrOffset - ZIP64_LOCATOR_SIZE,
|
|
ZIP64_LOCATOR_SIZE);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: cannot find the Zip64 central directory record"), name);
|
|
return 0;
|
|
}
|
|
uint32_t signature = get_u4 (b);
|
|
if (signature == 0x07064b50)
|
|
{ // Get an offset from the Zip64 cent-dir locator
|
|
// Zip64 end of central directory locator
|
|
// Offset Bytes Description
|
|
// 0 4 Zip64 end of central dir locator signature = 0x07064b50
|
|
// 4 4 number of the disk with the start of the zip64 end of central directory
|
|
// 8 8 relative offset of the Zip64 end of central directory record
|
|
// 12 4 total number of disks
|
|
Dprintf (DUMP_JAR_FILE, " cent-dir locator offset %10lld (0x%016llx)\n",
|
|
(long long) (ecdrOffset - ZIP64_LOCATOR_SIZE), (long long) (ecdrOffset - ZIP64_LOCATOR_SIZE));
|
|
ecdrOffset = get_u8 (b + 8);
|
|
}
|
|
else // the Zip64 end of central directory locator is absent
|
|
ecdrOffset -= ZIP64_CENT_DIR_SIZE;
|
|
Dprintf (DUMP_JAR_FILE, NTXT (" Zip64 end-cent-dir record offset: %10lld (0x%016llx)\n"),
|
|
(long long) ecdrOffset, (long long) ecdrOffset);
|
|
|
|
b = (unsigned char *) dwin->bind (ecdrOffset, ZIP64_CENT_DIR_SIZE);
|
|
if (b == NULL)
|
|
{
|
|
append_msg (CMSG_ERROR,
|
|
GTXT ("%s: cannot find the Zip64 central directory record"), name);
|
|
return 0;
|
|
}
|
|
// Zip64 end of central directory record
|
|
// Offset Bytes Description
|
|
// 0 4 Zip64 end of central dir signature = 0x06064b50
|
|
// 4 8 size of zip64 end of central directory record
|
|
// 12 2 version made by
|
|
// 14 2 version needed to extract
|
|
// 16 4 number of this disk
|
|
// 20 4 number of the disk with the start of the central directory
|
|
// 24 8 total number of entries in the central directory on this disk
|
|
// 32 8 total number of entries in the central directory
|
|
// 40 8 size of the central directory
|
|
// 48 8 offset of start of centraldirectory with respect to the starting disk number
|
|
// 56 Zip64 extensible data sector (variable size)
|
|
signature = get_u4 (b);
|
|
if (signature != 0x06064b50)
|
|
{
|
|
append_msg (CMSG_ERROR, GTXT ("%s: cannot find the Zip64 central directory record"), name);
|
|
return 0;
|
|
}
|
|
endCentDir->count = get_u8 (b + 32);
|
|
endCentDir->size = get_u8 (b + 40);
|
|
endCentDir->offset = get_u8 (b + 48);
|
|
Dprintf (DUMP_JAR_FILE,
|
|
NTXT (" cent-dir offset: %10lld (0x%016llx)\n"
|
|
" cent-dir size: %10lld (0x%016llx)\n"
|
|
" cent-dir entries: %10lld\n"),
|
|
(long long) endCentDir->offset, (long long) endCentDir->offset,
|
|
(long long) endCentDir->size, (long long) endCentDir->size,
|
|
(long long) endCentDir->count);
|
|
}
|
|
return 1;
|
|
}
|
|
|