reactos/sdk/tools/hhpcomp/chmc/chmc.c

1692 lines
40 KiB
C

/*
Copyright(C) 2010 Alex Andreotti <alex.andreotti@gmail.com>
This file is part of chmc.
chmc 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.
chmc 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 chmc. If not, see <http://www.gnu.org/licenses/>.
*/
#include "chmc.h"
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#if defined(_WIN32) || defined(__APPLE__)
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#else
#ifdef __REACTOS__
#include <sys/types.h>
#include <sys/stat.h>
#endif /* __REACTOS__ */
#include <unistd.h>
#endif
#include "err.h"
#include "encint.h"
#include <stdint.h>
#include "../lzx_compress/lzx_config.h"
#include "../lzx_compress/lzx_compress.h"
#define PACKAGE_STRING "hhpcomp development version"
/* if O_BINARY is not defined, the system is probably not expecting any such flag */
#ifndef O_BINARY
#define O_BINARY 0
#endif
int chmc_section_add(struct chmcFile *chm, const char *name);
struct chmcSection * chmc_section_create(struct chmcFile *chm,
const char *name);
void chmc_reset_table_init(struct chmcLzxcResetTable *reset_table);
void chmc_control_data_init(struct chmcLzxcControlData *control_data);
int chmc_namelist_create(struct chmcFile *chm, int len);
struct chmcTreeNode * chmc_add_meta(struct chmcFile *chm,
const char *metaname, int sect_id,
UChar *buf, UInt64 len);
struct chmcTreeNode *chmc_add_entry(struct chmcFile *chm, const char *name,
UInt16 prefixlen, int sect_id,
UChar *buf, UInt64 offset, UInt64 len);
void chmc_sections_free(struct chmcFile *chm);
void chmc_section_destroy(struct chmcSection *section);
void chmc_pmgi_free(struct chmcFile *chm);
void chmc_pmgl_free(struct chmcFile *chm);
void chmc_pmgl_destroy(struct chmcPmglChunkNode *node);
void chmc_pmgi_destroy(struct chmcPmgiChunkNode *node);
void chmc_entries_free(struct chmcFile *chm);
void chmc_entry_destroy(struct chmcTreeNode *node);
int chmc_add_tree(struct chmcFile *chm, const char *dir);
struct chmcTreeNode *chmc_add_file(struct chmcFile *chm, const char *filename,
UInt16 prefixlen, int sect_id, UChar *buf,
UInt64 len);
struct chmcTreeNode *chmc_add_dir(struct chmcFile *chm, const char *dir);
struct chmcTreeNode *chmc_add_empty(struct chmcFile *chm, const char *file);
int chmc_crunch_lzx(struct chmcFile *chm, int sect_id);
static int _lzx_at_eof(void *arg);
static int _lzx_put_bytes(void *arg, int n, void *buf);
static void _lzx_mark_frame(void *arg, uint32_t uncomp, uint32_t comp);
static int _lzx_get_bytes(void *arg, int n, void *buf);
int chmc_compressed_add_mark(struct chmcFile *chm, UInt64 at);
int chmc_control_data_done(struct chmcFile *chm);
int chmc_reset_table_done(struct chmcFile *chm);
void chmc_pmgl_done(struct chmcFile *chm);
void chmc_entries_qsort(struct chmcFile *chm);
static int _entry_cmp(const void *pva, const void *pvb);
struct chmcSection *chmc_section_lookup(struct chmcFile *chm, int id);
struct chmcPmglChunkNode *chmc_pmgl_create(void);
void chmc_pmgl_add(struct chmcFile *chm, struct chmcPmglChunkNode *pmgl);
void chmc_pmgl_init(struct chmcPmglChunkNode *node);
int chmc_pmgi_add_entry(struct chmcFile *chm, const char *name, int pmgl_id);
void chmc_pmgi_add(struct chmcFile *chm, struct chmcPmgiChunkNode *pmgi);
void chmc_string_init(struct chmcStringChunk *node);
#ifdef __REACTOS__
int chmc_uncompressed_done(struct chmcFile *chm);
int chmc_pmgi_done(struct chmcFile *chm);
int chmc_write(struct chmcFile *chm);
int chmc_appendfile(struct chmcFile *chm, const char *filename, void *buf,
size_t size );
int chmc_pmgl_add_entry(struct chmcFile *chm, struct chmcTreeNode *entry);
#endif /* __REACTOS__ */
struct chmcLzxInfo
{
struct chmcFile *chm;
struct chmcSection *section;
int fd;
UInt32 fd_offset;
UInt32 done;
UInt32 todo;
struct list_head *pos;
int error;
int eof;
};
static const short chmc_transform_list[] = {
0x7b, 0x37, 0x46, 0x43, 0x32, 0x38, 0x39,
0x34, 0x30, 0x2d, 0x39, 0x44, 0x33, 0x31,
0x2d, 0x31, 0x31, 0x44, 0x30 };
int chmc_init(struct chmcFile *chm, const char *filename,
struct chmcConfig *config)
{
struct chmcItsfHeader *itsf = &chm->itsf;
struct chmcSect0 *sect0 = &chm->sect0;
struct chmcItspHeader *itsp = &chm->itsp;
struct chmcSystem *system = &chm->system;
struct chmcSystemInfo *sysinfo = &chm->system.info;
struct chmcIndexHeader *idxhdr = &chm->idxhdr;
assert(chm);
assert(filename);
chmcerr_clean();
memset(chm, 0, sizeof(struct chmcFile));
chm->config = config;
if (strcmp(filename, "-") != 0) {
chm->fd = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644);
if (chm->fd < 0) {
chmcerr_set(errno, strerror(errno));
chmcerr_return_msg("creat file '%s'", filename);
}
} else {
chm->fd = fileno(stdout);
}
memcpy(itsf->signature, "ITSF", 4);
itsf->version = 3;
itsf->header_len = _CHMC_ITSF_V3_LEN;
itsf->unknown_000c = 1;
itsf->lang_id = chm->config->language;
memcpy(itsf->dir_uuid, CHMC_DIR_UUID, 16);
memcpy(itsf->stream_uuid, CHMC_STREAM_UUID, 16);
itsf->dir_offset = _CHMC_ITSF_V3_LEN + _CHMC_SECT0_LEN;
itsf->sect0_offset = _CHMC_ITSF_V3_LEN;
itsf->sect0_len = _CHMC_SECT0_LEN;
sect0->file_len = _CHMC_ITSF_V3_LEN
+ _CHMC_SECT0_LEN
+ _CHMC_ITSP_V1_LEN;
sect0->unknown_0000 = 510;
memcpy(itsp->signature, "ITSP", 4);
itsp->version = 1;
itsp->header_len = _CHMC_ITSP_V1_LEN;
itsp->unknown_000c = 10;
itsp->block_len = _CHMC_CHUNK_LEN;
itsp->blockidx_intvl = CHM_IDX_INTVL;
itsp->index_depth = 2;
itsp->unknown_0028 = -1;
itsp->lang_id = CHMC_MS_LCID_EN_US;
memcpy(itsp->system_uuid, CHMC_SYSTEM_UUID, 16);
itsp->header_len2 = _CHMC_ITSP_V1_LEN;
memset(itsp->unknown_0048, -1, 12);
system->version = 3;
system->_size = _CHMC_SYSTEM_HDR_LEN + sizeof(struct chmcIndexHeader);
sysinfo->lcid = CHMC_MS_LCID_EN_US;
memcpy(idxhdr->signature, "T#SM", 4);
idxhdr->unknown_4 = 28582569; // FIXME got from some chm
idxhdr->unknown_8 = 1;
// idxhdr->full_search = 1;
// idxhdr->klinks = 1;
// idxhdr->alinks = 0;
// idxhdr->timestamp = ???;
// idxhdr->num_of_topic = 2; // sorry??
idxhdr->off_img_list = -1;
// idxhdr->img_type_folder;
idxhdr->background = -1;
idxhdr->foreground = -1;
idxhdr->off_font = -1;
idxhdr->win_style = -1;
idxhdr->ex_win_style = -1;
idxhdr->unknown_34 = -1;
idxhdr->off_frame_name = -1;
idxhdr->off_win_name = -1;
// idxhdr->num_of_info;
idxhdr->unknown_44 = 1;
// idxhdr->num_of_merge_files;
// idxhdr->unknown_4c;
INIT_LIST_HEAD(&chm->sections_list);
INIT_LIST_HEAD(&chm->pmgl_list);
INIT_LIST_HEAD(&chm->entries_list);
INIT_LIST_HEAD(&chm->pmgi_list);
chm->strings = malloc(4096);
memset(chm->strings, 0, 4096);
chm->strings_len = 4096;
chm->strings_offset = 1;
if (chmc_section_add(chm, "Uncompressed") != CHMC_NOERR)
chmcerr_return_msg("adding section: Uncompressed");
if (chmc_section_add(chm, "MSCompressed") != CHMC_NOERR)
chmcerr_return_msg("adding section: MSCompressed");
chmc_sections_done(chm);
return CHMC_NOERR;
}
int chmc_section_add(struct chmcFile *chm, const char *name)
{
struct chmcSection *section;
assert(chm);
assert(name);
section = chmc_section_create(chm, name);
if (!section)
return chmcerr_code();
list_add_tail(&section->list, &chm->sections_list);
chm->sections_num++;
return CHMC_NOERR;
}
struct chmcSection *chmc_section_create(struct chmcFile *chm,
const char *name)
{
struct chmcSection *section;
assert(name);
section = calloc(1, sizeof(struct chmcSection));
if (section) {
const char *tmpdir;
int len;
len = strlen(name);
memcpy(section->name, name, len + 1);
section->offset = 0;
section->len = 0;
tmpdir = NULL;
if (chm->config != NULL)
tmpdir = chm->config->tmpdir;
if (tmpdir == NULL)
tmpdir = "/tmp/";
len = strlen(tmpdir);
if (len >= PATH_MAX - 12) {
chmcerr_set(errno, strerror(errno));
chmcerr_msg("tmpdir too long: '%s'", tmpdir);
goto fail;
}
strcat(section->filename, tmpdir);
if (section->filename[len - 1] != '/')
strcat(section->filename, "/");
if (strcmp("MSCompressed", name) == 0)
strcat(section->filename, "chmcCXXXXXX");
else
strcat(section->filename, "chmcUXXXXXX");
section->fd = mkstemps(section->filename, 0);
fprintf(stderr, "temp file: %s\n", section->filename);
if (section->fd < 0) {
chmcerr_set(errno, strerror(errno));
chmcerr_msg("creat() file '%s'", section->filename);
goto fail;
}
else if (strcmp(section->name, "MSCompressed") == 0) {
chmc_reset_table_init(&section->reset_table_header);
chmc_control_data_init(&section->control_data);
INIT_LIST_HEAD(&section->mark_list);
section->mark_count = 0;
}
} else {
chmcerr_set(errno, strerror(errno));
chmcerr_msg("section '%s' allocation failed", name);
}
return section;
fail:
free(section);
return NULL;
}
void chmc_reset_table_init(struct chmcLzxcResetTable *reset_table)
{
reset_table->version = 2;
reset_table->block_count = 0;
reset_table->entry_size = 8;
reset_table->table_offset = _CHMC_LZXC_RESETTABLE_V1_LEN;
reset_table->uncompressed_len = 0;
reset_table->compressed_len = 0;
reset_table->block_len = 0x8000;
}
void chmc_control_data_init(struct chmcLzxcControlData *control_data)
{
control_data->size = 6;
memcpy(control_data->signature, "LZXC", 4);
control_data->version = 2;
control_data->resetInterval = 2;
control_data->windowSize = 2;
control_data->windowsPerReset = 1;
control_data->unknown_18 = 0;
}
void chmc_sections_done(struct chmcFile *chm)
{
int len;
int i;
assert(chm);
chm->sections = malloc(sizeof(struct chmcSection *) * chm->sections_num);
if (chm->sections) {
struct chmcSection *section;
struct list_head *pos;
i = 0;
len = 4;
list_for_each(pos, &chm->sections_list) {
section = list_entry(pos, struct chmcSection, list);
len += 4 + strlen(section->name) * 2;
chm->sections[i++] = section;
}
chmc_namelist_create(chm, len);
} else
BUG_ON("FIXME: %s: %d\n", __FILE__, __LINE__);
}
int chmc_namelist_create(struct chmcFile *chm, int len)
{
UInt16 *namelist;
namelist = malloc(len);
if (namelist) {
struct chmcSection *section;
int i, j, k, name_len;
k = 0;
namelist[k++] = len >> 1;
namelist[k++] = chm->sections_num;
for( i=0; i < chm->sections_num; i++ ) {
section = chm->sections[i];
name_len = strlen(section->name);
namelist[k++] = name_len;
for( j=0; j < name_len; j++ )
namelist[k++] = section->name[j];
namelist[k++] = 0;
}
chmc_add_meta(chm, "::DataSpace/NameList", 0, (UChar *)namelist, len);
}
else
return CHMC_ENOMEM;
return CHMC_NOERR;
}
struct chmcTreeNode *chmc_add_empty(struct chmcFile *chm, const char *file)
{
assert(chm);
return chmc_add_entry(chm, file, 0, 0, NULL, 0, 0);
}
struct chmcTreeNode *chmc_add_meta(struct chmcFile *chm, const char *metaname,
int sect_id,
UChar *buf, UInt64 len)
{
struct chmcSection *section;
struct chmcTreeNode *node;
assert(chm);
if (sect_id >= chm->sections_num)
return NULL;
section = chm->sections[sect_id];
node = chmc_add_entry(chm, metaname, 0, sect_id, buf, section->offset, len);
if ((node) && (len > 0))
section->offset += len;
return node;
}
struct chmcTreeNode *chmc_add_entry(struct chmcFile *chm, const char *name,
UInt16 prefixlen, int sect_id, UChar *buf,
UInt64 offset, UInt64 len)
{
struct chmcTreeNode *node;
assert(chm);
if (sect_id >= (chm->sections_num)) {
fprintf(stderr,"sect_id %d >= chm->sections_num %d\n",
sect_id, chm->sections_num);
return NULL;
}
node = malloc(sizeof(struct chmcTreeNode));
if (node) {
node->flags = 0;
node->name = strdup( name );
node->prefixlen = prefixlen;
node->sect_id = sect_id;
node->buf = buf;
node->offset = offset;
node->len = len;
list_add_tail(&node->list, &chm->entries_list);
chm->entries_num++;
}
else
BUG_ON("FIXME: %s: %d\n", __FILE__, __LINE__);
return node;
}
void chmc_term(struct chmcFile *chm)
{
assert(chm);
assert(chm->fd > -1);
free(chm->strings);
chmc_entries_free(chm);
chmc_pmgl_free(chm);
chmc_pmgi_free(chm);
if (chm->sections)
free(chm->sections);
chmc_sections_free(chm);
if (chm->fd != fileno(stdout))
close(chm->fd);
}
void chmc_sections_free(struct chmcFile *chm)
{
struct chmcSection *section;
struct list_head *pos, *q;
assert(chm);
list_for_each_safe(pos, q, &chm->sections_list) {
section = list_entry(pos, struct chmcSection, list);
list_del(pos);
chmc_section_destroy(section);
}
}
void chmc_section_destroy(struct chmcSection *section)
{
assert(section);
assert(section->fd > -1);
if (strcmp(section->name, "MSCompressed") == 0) {
struct list_head *pos, *q;
struct chmcResetTableMark *mark;
list_for_each_safe(pos, q, &section->mark_list) {
mark = list_entry(pos, struct chmcResetTableMark, list);
list_del(pos);
free(mark);
}
}
close(section->fd);
unlink(section->filename);
free(section);
}
void chmc_pmgi_free(struct chmcFile *chm)
{
struct chmcPmgiChunkNode *node;
struct list_head *pos, *q;
assert(chm);
list_for_each_safe(pos, q, &chm->pmgi_list) {
node = list_entry(pos, struct chmcPmgiChunkNode, list);
list_del(pos);
chmc_pmgi_destroy(node);
}
}
void chmc_pmgl_free(struct chmcFile *chm)
{
struct chmcPmglChunkNode *node;
struct list_head *pos, *q;
assert(chm);
list_for_each_safe(pos, q, &chm->pmgl_list) {
node = list_entry(pos, struct chmcPmglChunkNode, list);
list_del(pos);
chmc_pmgl_destroy(node);
}
}
void chmc_entries_free( struct chmcFile *chm )
{
struct chmcTreeNode *node;
struct list_head *pos, *q;
assert(chm);
list_for_each_safe(pos, q, &chm->entries_list) {
node = list_entry(pos, struct chmcTreeNode, list);
list_del(pos);
chmc_entry_destroy(node);
}
free(chm->sort_entries);
}
UInt32 chmc_strings_add( struct chmcFile *chm, const char *s)
{
UInt32 len, off;
/* FIXME null are errors */
if (!s || *s == '\0')
return 0;
len = strlen(s);
off = chm->strings_offset;
if (off + len + 1 < chm->strings_len) {
memcpy(&chm->strings[off], s, len + 1);
chm->strings_offset += len + 1;
} else {
/* realloc strings */
/* if the string truncate copy til end of chunk
then re-copy from 0 of new */
BUG_ON("FIXME: %s: %d: handle more chunk for strings\n",
__FILE__, __LINE__);
}
return off;
}
void chmc_entry_destroy( struct chmcTreeNode *node )
{
assert(node);
assert(node->name);
free(node->name);
if (node->buf && !(node->flags & CHMC_TNFL_STATIC))
free(node->buf);
free(node);
}
struct chmcTreeNode *chmc_add_file(struct chmcFile *chm, const char *filename,
UInt16 prefixlen, int sect_id, UChar *buf,
UInt64 len)
{
struct chmcSection *section;
struct chmcTreeNode *node;
assert(chm);
if (sect_id >= chm->sections_num)
return NULL;
section = chm->sections[sect_id];
node = chmc_add_entry(chm, filename, prefixlen, sect_id, NULL,
section->offset, len);
if ((node) && (len > 0))
section->offset += len;
return node;
}
struct chmcTreeNode *chmc_add_dir(struct chmcFile *chm, const char *dir)
{
assert(chm);
return chmc_add_entry(chm, dir, 0, 0, NULL, 0, 0);
}
static inline void *chmc_syscat_mem(void *d, void *s, unsigned long len)
{
memcpy(d, s, len);
return (char *)d + len;
}
static void *chmc_syscat_entry(Int16 code, void *d, void *s, Int16 len)
{
d = chmc_syscat_mem(d, &code, 2);
d = chmc_syscat_mem(d, &len, 2);
return chmc_syscat_mem(d, s, len);
}
/* #define DEFAULT_TOPIC "index.htm" */
/* #define TITLE "hello world" */
/* #define LCASEFILE "test" */
int chmc_system_done(struct chmcFile *chm)
{
struct chmcSystem *system;
struct chmcSystemInfo *sysinfo;
struct chmcIndexHeader *idxhdr;
void *sysp, *p;
assert(chm);
system = &chm->system;
sysinfo = &system->info;
idxhdr = &chm->idxhdr;
// TODO should be set from application
// system->_size += (_CHMC_SYS_ENTRY_HDR_LEN + sizeof(UInt32)) /* timestamp */
// + (_CHMC_SYS_ENTRY_HDR_LEN + sizeof(PACKAGE_STRING)) /* compiler */
// + (_CHMC_SYS_ENTRY_HDR_LEN + sizeof(UInt32)) /* eof */
// + (_CHMC_SYS_ENTRY_HDR_LEN + sizeof(DEFAULT_TOPIC))
// + (_CHMC_SYS_ENTRY_HDR_LEN + sizeof(TITLE))
// + 32;
sysp = malloc(16384);
if (sysp) {
UInt32 val;
UInt16 code, len;
const char *entry_val;
p = chmc_syscat_mem(sysp, &system->version, sizeof(system->version));
val = 0;
p = chmc_syscat_entry(SIEC_TIMESTAMP, p, &val, sizeof(val));
p = chmc_syscat_entry(SIEC_COMPVER, p,
/*"HHA Version 4.74.8702"*/
PACKAGE_STRING,
sizeof(PACKAGE_STRING)
/*strlen("HHA Version 4.74.8702")+1*/);
p = chmc_syscat_entry(SIEC_SYSINFO, p,
sysinfo, sizeof(struct chmcSystemInfo));
if (chm->config != NULL && chm->config->deftopic != NULL)
entry_val = chm->config->deftopic;
else
entry_val = "index.htm";
p = chmc_syscat_entry(SIEC_DEFTOPIC, p, (void *)entry_val,
strlen(entry_val)+1);
if (chm->config != NULL && chm->config->title != NULL)
entry_val = chm->config->title;
else
entry_val = "untitled";
p = chmc_syscat_entry(SIEC_TITLE, p, (void *)entry_val,
strlen(entry_val)+1);
// p = chmc_syscat_entry(SIEC_DEFFONT, p, &val, sizeof(val));
p = chmc_syscat_entry(SIEC_LCASEFILE, p, "siec_lcasefile",
strlen("siec_lcasefile")+1);
p = chmc_syscat_entry(SIEC_DEFWINDOW, p,
"MsdnHelp", strlen("MsdnHelp")+1);
val = 0;
p = chmc_syscat_entry(SIEC_NUMOFINFOT, p, &val, sizeof(val));
p = chmc_syscat_entry(SIEC_IDXHDR, p,
idxhdr, sizeof(struct chmcIndexHeader));
val = 0;
p = chmc_syscat_entry(SIEC_INFOCHKSUM, p, &val, sizeof(val));
system->_size = (char *)p - (char *)sysp;
chmc_add_meta(chm, "/#SYSTEM", 0, sysp, system->_size);
return CHMC_NOERR;
}
chmcerr_set(CHMC_ENOMEM, "system done: malloc %d bytes",
system->_size);
return CHMC_ENOMEM;
}
int chmc_tree_done( struct chmcFile *chm )
{
struct chmcItsfHeader *itsf;
struct chmcSect0 *sect0;
struct chmcItspHeader *itsp;
struct chmcTreeNode *ctrl;
UInt32 str_index;
const char *val;
assert(chm);
itsf = &chm->itsf;
sect0 = &chm->sect0;
itsp = &chm->itsp;
chmc_add_dir(chm, "/");
ctrl = chmc_add_meta(chm, "::DataSpace/Storage/MSCompressed/Transform/List",
0, (UChar *)chmc_transform_list,
sizeof(chmc_transform_list));
if (ctrl)
ctrl->flags |= CHMC_TNFL_STATIC;
chmc_system_done(chm);
if (chm->config != NULL && chm->config->deftopic != NULL)
val = chm->config->deftopic;
else
val = "index.htm";
str_index = chmc_strings_add(chm, val);
#if 0
// FIXME just a test
{
UChar *p;
int len;
struct chmcTopicEntry topicEntry;
// struct chmcUrlStrEntry urlStrEntry;
p = malloc(4096);
if (p) {
memset(p, 0, 4096);
len = 0;
topicEntry.tocidx_offset = 4096;
topicEntry.strings_offset = -1;
topicEntry.urltbl_offset = 0;
topicEntry.in_content = 6;
topicEntry.unknown = 0;
memcpy(p, &topicEntry, sizeof(struct chmcTopicEntry));
len += sizeof(struct chmcTopicEntry);
chm->idxhdr.num_of_topic++;
chmc_add_meta(chm, "/#TOPICS", 1, (UChar *)p, len);
} else
BUG_ON("FIXME: %s: %d\n", __FILE__, __LINE__);
}
#endif
ctrl = chmc_add_meta(chm, "/#IDXHDR", 1, (void *)&chm->idxhdr,
sizeof(struct chmcIndexHeader));
if (ctrl)
ctrl->flags |= CHMC_TNFL_STATIC;
{
UInt32 *p;
p = malloc(8+196);
if (p) {
const char *val;
memset(p+2, 0, 196);
p[0] = 1;
p[1] = 196;
p[2+0] = 196;
// p[2+2] = 1;
// p[2+3] = 0x00000532;
// p[2+4] = 0x00062520;
// p[2+8] = 86;
// p[2+9] = 51;
// p[2+10] = 872;
// p[2+11] = 558;
// p[2+19] = 220;
// p[2+27] = 0x00000041;
// p[2+28] = 14462;
if (chm->config != NULL && chm->config->title != NULL)
val = chm->config->title;
else
val = "untitled";
p[2+5] = chmc_strings_add(chm, val);
if (chm->config != NULL && chm->config->hhc != NULL)
val = chm->config->hhc;
else
val = "toc.hhc";
p[2+24] = chmc_strings_add(chm, val);
if (chm->config != NULL && chm->config->hhk != NULL)
val = chm->config->hhc;
else
val = "toc.hhk";
p[2+25] = chmc_strings_add(chm, val);
p[2+26] = str_index;
chmc_add_meta(chm, "/#WINDOWS", 1, (UChar *)p, 8+196);
} else
BUG_ON("FIXME: %s: %d\n", __FILE__, __LINE__);
}
ctrl = chmc_add_meta(chm, "/#STRINGS", 1, (void *)chm->strings,
chm->strings_len);
if (ctrl)
ctrl->flags |= CHMC_TNFL_STATIC;
#if 0
// FIXME just a test
{
UChar *p;
int len;
struct chmcUrlStrEntry urlStrEntry;
urlStrEntry.url_offset = 0;
urlStrEntry.framename_offset = 0;
p = malloc(4096);
if (p) {
memset(p, 0, 4096);
*p = 0x42;
len = 1;
memcpy(p + len, &urlStrEntry, sizeof(struct chmcUrlStrEntry));
len += sizeof(struct chmcUrlStrEntry);
len += sprintf(p + len, "index.htm" ) + 1;
memcpy(p + len, &urlStrEntry, sizeof(struct chmcUrlStrEntry));
len += sizeof(struct chmcUrlStrEntry);
len += sprintf(p + len, "test.htm" ) + 1;
chmc_add_meta(chm, "/#URLSTR", 1, (UChar *)p, len);
} else
BUG_ON("FIXME: %s: %d\n", __FILE__, __LINE__);
}
#endif
// chmc_add_entry(chm, "/#URLTBL", 0, 1, NULL, 0, 0);
// chmc_add_entry(chm, "/#TOPICS", 0, 1, NULL, 0, 0);
// NOTE NOTE NOTE add any meta compressed before crunch ;-)
chmc_crunch_lzx(chm, 1);
chmc_control_data_done(chm);
chmc_reset_table_done(chm);
chmc_add_empty(chm, "/#ITBITS");
// NOTE in this implementation compressed Content should be the last file
// added to section 0
chmc_add_meta(chm, "::DataSpace/Storage/MSCompressed/Content", 0, NULL,
chm->sections[1]->offset);
chmc_entries_qsort(chm);
chmc_uncompressed_done(chm);
chmc_pmgl_done(chm);
chmc_pmgi_done(chm);
itsf->dir_len = _CHMC_ITSP_V1_LEN
+ (_CHMC_CHUNK_LEN * itsp->num_blocks);
itsf->data_offset = _CHMC_ITSF_V3_LEN
+ _CHMC_SECT0_LEN
+ _CHMC_ITSP_V1_LEN
+ (_CHMC_CHUNK_LEN * itsp->num_blocks);
sect0->file_len += _CHMC_CHUNK_LEN * itsp->num_blocks;
chmc_write(chm);
{
struct chmcSection *section;
struct list_head *pos;
UChar buf[4096];
list_for_each(pos, &chm->sections_list) {
section = list_entry(pos, struct chmcSection, list);
chmc_appendfile(chm, section->filename, buf, 4096);
}
}
return CHMC_NOERR;
}
int chmc_crunch_lzx(struct chmcFile *chm, int sect_id)
{
struct chmcLzxInfo lzx_info;
lzx_data *lzxd;
int subd_ok = 1;
int do_reset = 1;
int block_size;
lzx_results lzxr;
int wsize_code = 16;
assert(chm);
if ((wsize_code < 15) || (wsize_code > 21)) {
fprintf(stderr, "window size must be between 15 and 21 inclusive\n");
return CHMC_EINVAL;
}
lzx_info.chm = chm;
lzx_info.section = chm->sections[sect_id];
lzx_info.done = 0;
lzx_info.todo = lzx_info.section->offset;
lzx_info.pos = chm->entries_list.next;
lzx_info.error = 0;
lzx_info.eof = 0;
lzx_info.fd = -1;
lzx_info.fd_offset = 0;
chmc_compressed_add_mark(lzx_info.chm, 0);
lzx_info.section->reset_table_header.block_count++;
/* undocumented fact, according to Caie --
block size cannot exceed window size. (why not?) */
/* The block size must not be larger than the window size.
While the compressor will create apparently-valid LZX files
if this restriction is violated, some decompressors
will not handle them. */
block_size = 1 << wsize_code;
// lzx_info.section->control_data.windowSize = wsize_code;
// lzx_info.section->control_data.windowsPerReset = block_size;
lzx_init(&lzxd, wsize_code,
_lzx_get_bytes, &lzx_info, _lzx_at_eof,
_lzx_put_bytes, &lzx_info,
_lzx_mark_frame, &lzx_info);
while(! _lzx_at_eof(&lzx_info)) {
if (do_reset)
lzx_reset(lzxd);
lzx_compress_block(lzxd, block_size, subd_ok);
}
lzx_finish(lzxd, &lzxr);
return CHMC_NOERR;
}
static int _lzx_at_eof(void *arg)
{
struct chmcLzxInfo *lzx_info = (struct chmcLzxInfo *)arg;
return lzx_info->error || lzx_info->done >= lzx_info->todo || lzx_info->eof;
}
static int _lzx_put_bytes(void *arg, int n, void *buf)
{
struct chmcLzxInfo *lzx_info = (struct chmcLzxInfo *)arg;
struct chmcSect0 *sect0 = &lzx_info->chm->sect0;
int wx;
static int counter = 0;
counter += n;
wx = write(lzx_info->section->fd, buf, n);
sect0->file_len += wx;
lzx_info->section->len += wx;
return wx;
}
static void _lzx_mark_frame(void *arg, uint32_t uncomp, uint32_t comp)
{
struct chmcLzxInfo *lzx_info = (struct chmcLzxInfo *)arg;
struct chmcSection *section = lzx_info->chm->sections[1];
UInt64 compressed;
chmc_dump( "Aligned data at %d(in compressed stream, %d) (%lu/%lu)\n",
uncomp, comp, (unsigned long)lzx_info->done, (unsigned long)lzx_info->todo );
compressed = comp;
section->reset_table_header.block_count++;
chmc_compressed_add_mark( lzx_info->chm, compressed );
section->reset_table_header.uncompressed_len = uncomp;
section->reset_table_header.compressed_len = comp;
}
static int _lzx_get_bytes(void *arg, int n, void *buf)
{
struct chmcLzxInfo *lzx_info = (struct chmcLzxInfo *)arg;
struct chmcFile *chm = lzx_info->chm;
struct chmcTreeNode *node;
int todo;
int done;
int toread;
int rx;
todo = n;
done = 0;
// compression state machine
// lzx compressor ask for block input bytes
// need to keep current entry file and offset trought blocks
// until last entry
while (todo) {
// end of entries reached?
if (lzx_info->pos == &chm->entries_list) {
lzx_info->eof = 1;
break;
}
node = list_entry( lzx_info->pos, struct chmcTreeNode, list );
// skip empty files and directories
if (node->len == 0
|| strcmp("MSCompressed", chm->sections[node->sect_id]->name)) {
lzx_info->pos = lzx_info->pos->next;
continue;
}
else
if (node->buf) {
// have len and buffer, it's mallocated not file
}
else
if (lzx_info->fd == -1) {
// open file if it isn't
lzx_info->fd = open(node->name, O_RDONLY | O_BINARY);
if (lzx_info->fd < 0) {
chmc_error("%s: %d: error %d: '%s' %s\n",
__FILE__, __LINE__,
errno, node->name, strerror(errno));
lzx_info->error = 1;
break;
}
}
// read till the end of the file or till the lzx buffer is filled
toread = node->len - lzx_info->fd_offset;
if (toread > todo)
toread = todo;
if (toread <= 0)
continue;
// read input
if (node->buf) {
memcpy((char *)buf + (n - todo), &node->buf[lzx_info->fd_offset], toread);
rx = toread;
}
else
{
rx = read(lzx_info->fd, (char *)buf + (n - todo), toread);
if (rx <= 0) {
int temp = errno;
chmc_error("read error %s \n", strerror(temp));
lzx_info->error = 2;
break;
}
}
todo -= rx;
lzx_info->fd_offset += rx;
done += rx;
lzx_info->done += rx;
// end of current file reached, goto next entry
if (lzx_info->fd_offset == node->len) {
if (lzx_info->fd > -1)
close(lzx_info->fd);
lzx_info->fd = -1;
lzx_info->fd_offset = 0;
lzx_info->pos = lzx_info->pos->next;
}
}
return done;
}
int chmc_compressed_add_mark(struct chmcFile *chm, UInt64 at)
{
struct chmcSection *section;
struct chmcResetTableMark *mark;
assert(chm);
section = chm->sections[1];
mark = malloc(_CHMC_RSTTBL_MARK);
if (mark) {
mark->at = at;
chmc_dump("[%d] at: %jd\n", section->mark_count, at);
list_add_tail(&mark->list, &section->mark_list);
section->mark_count++;
return CHMC_NOERR;
}
return CHMC_ENOMEM;
}
int chmc_control_data_done(struct chmcFile *chm)
{
struct chmcTreeNode *ctrl;
ctrl = chmc_add_meta(chm, "::DataSpace/Storage/MSCompressed/ControlData",
0, (UChar *)&chm->sections[1]->control_data,
_CHMC_LZXC_V2_LEN);
if (ctrl) {
ctrl->flags |= CHMC_TNFL_STATIC;
return CHMC_NOERR;
}
return CHMC_ENOMEM;
}
int chmc_reset_table_done(struct chmcFile *chm)
{
struct chmcSection *section;
struct chmcLzxcResetTable *reset_table;
struct list_head *pos;
struct chmcResetTableMark *mark;
UInt64 *at;
int i, len;
section = chm->sections[1];
len = _CHMC_LZXC_RESETTABLE_V1_LEN + (section->mark_count * sizeof(UInt64));
reset_table = malloc(len);
if (reset_table) {
memcpy(reset_table, &section->reset_table_header,
_CHMC_LZXC_RESETTABLE_V1_LEN);
at = (void *)((char *)reset_table + _CHMC_LZXC_RESETTABLE_V1_LEN);
i = 0;
list_for_each(pos, &section->mark_list) {
mark = list_entry(pos, struct chmcResetTableMark, list);
at[i++] = mark->at;
}
chmc_add_dir(chm, "::DataSpace/Storage/MSCompressed/Transform/"
"{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/");
chmc_add_meta(chm, "::DataSpace/Storage/MSCompressed/Transform/"
"{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}"
"/InstanceData/ResetTable",
0, (UChar *)reset_table, len);
{ // TODO FIXME do better
UInt64 *uncompressed_len = malloc(8);
if (uncompressed_len) {
*uncompressed_len = reset_table->uncompressed_len;
chmc_add_meta(chm, "::DataSpace/Storage/MSCompressed/SpanInfo",
0, (UChar *)uncompressed_len, 8);
}
}
return CHMC_NOERR;
}
return CHMC_ENOMEM;
}
void chmc_entries_qsort(struct chmcFile *chm)
{
struct chmcTreeNode *node;
struct list_head *pos;
int i;
assert(chm);
chm->sort_entries = malloc(sizeof(struct chmcTreeNode *)
* chm->entries_num);
i = 0;
list_for_each(pos, &chm->entries_list) {
node = list_entry(pos, struct chmcTreeNode, list);
chm->sort_entries[i++] = node;
}
qsort(chm->sort_entries, chm->entries_num, sizeof(struct chmcTreeNode *),
_entry_cmp);
}
static int _entry_cmp(const void *pva, const void *pvb)
{
const struct chmcTreeNode * const *pa = pva;
const struct chmcTreeNode * const *pb = pvb;
const struct chmcTreeNode *a = *pa, *b = *pb;
return strcmp( &a->name[a->prefixlen], &b->name[b->prefixlen] );
}
int chmc_uncompressed_done(struct chmcFile *chm)
{
struct chmcSect0 *sect0 = &chm->sect0;
struct chmcTreeNode *node;
struct list_head *pos;
int wx;
list_for_each(pos, &chm->entries_list) {
node = list_entry( pos, struct chmcTreeNode, list );
if (strcmp( "MSCompressed", chm->sections[node->sect_id]->name ) == 0)
continue;
if ((node->buf) && (node->len > 0)) {
wx = write(chm->sections[node->sect_id]->fd, node->buf, node->len);
sect0->file_len += wx;
}
}
return CHMC_NOERR;
}
void chmc_pmgl_done(struct chmcFile *chm)
{
struct chmcTreeNode *entry;
int i;
assert(chm);
for(i=0; i < chm->entries_num; i++) {
entry = chm->sort_entries[i];
chmc_pmgl_add_entry(chm, entry);
}
}
int chmc_pmgl_add_entry(struct chmcFile *chm, struct chmcTreeNode *entry)
{
struct chmcPmglChunkNode *pmgl;
struct chmcPmglChunk *chunk;
struct chmcSection *section;
struct chmcItspHeader *itsp = &chm->itsp;
UChar *p;
UInt16 *idx;
int name_len;
int outlen;
int should_idx, idx_intlv;
int free;
assert(chm);
assert(entry);
// check section bound
section = chmc_section_lookup(chm, entry->sect_id);
if (!section)
chmcerr_set_return(CHMC_ENOMEM, "section %d lookup failed: ",
entry->sect_id);
// check chunk space for new entry
name_len = strlen(&entry->name[entry->prefixlen]);
outlen = chmc_encint_len(name_len);
outlen += name_len;
outlen += chmc_encint_len(entry->sect_id);
outlen += chmc_encint_len(entry->offset);
outlen += chmc_encint_len(entry->len);
// look for current pmgl chunk, create if doesn't exist
if (!chm->pmgl_last) {
pmgl = chmc_pmgl_create();
if (pmgl)
chmc_pmgl_add(chm, pmgl);
else
chmcerr_set_return(CHMC_ENOMEM, "pmgl chunk: ");
}
else
pmgl = chm->pmgl_last;
do {
chunk = &chm->pmgl_last->chunk;
idx_intlv = 1 + ( 1 << itsp->blockidx_intvl );
should_idx = ( ( chunk->entries_count > 0 )
&& ! ( ( chunk->entries_count + 1 ) % idx_intlv )
? 2 : 0 );
free = sizeof(chunk->data) - pmgl->data_len - pmgl->index_len
- should_idx;
// current(last) chunk doesn't have enough room? force new one
if (outlen + should_idx > free) {
//chm->pmgl_last = NULL;
pmgl = chmc_pmgl_create();
if ( pmgl )
chmc_pmgl_add(chm, pmgl);
else
chmcerr_set_return(CHMC_ENOMEM, "pmgl chunk: ");
continue;
}
p = (void *)&chunk->data[pmgl->data_len];
if (should_idx) {
idx = (void *)((char *)&chunk->data[CHMC_PMGL_DATA_LEN] - pmgl->index_len);
*idx = (char *)p - (char *)&chunk->data;
}
p += chmc_encint(name_len, p);
memcpy(p, &entry->name[entry->prefixlen], name_len);
p += name_len;
p += chmc_encint(entry->sect_id, p);
p += chmc_encint(entry->offset, p);
p += chmc_encint(entry->len, p);
pmgl->data_len += outlen;
pmgl->index_len += should_idx;
chunk->entries_count++;
chunk->header.free_space -= outlen;
break;
} while (1);
return CHMC_NOERR;
}
struct chmcSection *chmc_section_lookup(struct chmcFile *chm, int id)
{
struct chmcSection *current;
struct list_head *pos;
int i;
assert(chm);
i = 0;
list_for_each(pos, &chm->sections_list) {
current = list_entry(pos, struct chmcSection, list);
if (i == id)
return current;
i++;
}
return NULL;
}
struct chmcPmglChunkNode *chmc_pmgl_create(void)
{
struct chmcPmglChunkNode *node;
node = malloc(sizeof(struct chmcPmglChunkNode));
if (node)
chmc_pmgl_init(node);
return node;
}
void chmc_pmgl_init(struct chmcPmglChunkNode *node)
{
struct chmcPmglChunk *chunk;
assert(node);
node->data_len = 0;
node->index_len = 0;
chunk = &node->chunk;
memcpy(chunk->header.signature, "PMGL", 4);
// FIXME check it is the right len
chunk->header.free_space = CHMC_PMGL_DATA_LEN + 2;
chunk->header.unknown_0008 = 0;
chunk->header.block_prev = -1;
chunk->header.block_next = -1;
memset(chunk->data, 0, CHMC_PMGL_DATA_LEN);
}
void chmc_pmgi_init(struct chmcPmgiChunkNode *node)
{
struct chmcPmgiChunk *chunk;
assert(node);
node->data_len = 0;
node->index_len = 0;
chunk = &node->chunk;
memcpy(chunk->header.signature, "PMGI", 4);
// FIXME check it is the right len
chunk->header.free_space = CHMC_PMGI_DATA_LEN + 2;
// chunk->header.unknown_0008 = 0;
// chunk->header.block_prev = -1;
// chunk->header.block_next = -1;
memset(chunk->data, 0, CHMC_PMGI_DATA_LEN);
}
struct chmcPmgiChunkNode *chmc_pmgi_create(void)
{
struct chmcPmgiChunkNode *node;
node = malloc(sizeof(struct chmcPmgiChunkNode));
if (node)
chmc_pmgi_init(node);
return node;
}
void chmc_pmgl_destroy(struct chmcPmglChunkNode *node)
{
assert(node);
free(node);
}
void chmc_pmgi_destroy(struct chmcPmgiChunkNode *node)
{
assert(node);
free(node);
}
void chmc_pmgl_add(struct chmcFile *chm, struct chmcPmglChunkNode *pmgl)
{
struct chmcItspHeader *itsp = &chm->itsp;
struct chmcPmglHeader *hdr;
assert(chm);
assert(pmgl);
list_add_tail(&pmgl->list, &chm->pmgl_list);
itsp->index_last = itsp->num_blocks;
hdr = &pmgl->chunk.header;
hdr->block_prev = itsp->num_blocks - 1;
if (chm->pmgl_last) {
hdr = &chm->pmgl_last->chunk.header;
hdr->block_next = itsp->num_blocks;
}
itsp->num_blocks++;
chm->pmgl_last = pmgl;
}
int chmc_pmgi_done(struct chmcFile *chm)
{
struct chmcItspHeader *itsp = &chm->itsp;
struct chmcPmglChunkNode *pmgl;
struct list_head *pos;
int i, j;
char name[256]; //FIXME use malloc
UInt32 name_len;
assert(chm);
// only one pml, omitted pmgi
if (itsp->num_blocks == 1) {
itsp->index_depth = 1;
itsp->index_root = -1;
itsp->index_last = 0;
return CHMC_NOERR;
}
itsp->index_root = itsp->num_blocks;
i = 0;
list_for_each(pos, &chm->pmgl_list) {
pmgl = list_entry(pos, struct chmcPmglChunkNode, list);
j = chmc_decint(&pmgl->chunk.data[0], &name_len);
if (name_len <= 255) {
memcpy(name, &pmgl->chunk.data[j], name_len);
name[name_len] = '\0';
chmc_pmgi_add_entry(chm, name, i);
}
else
BUG_ON("name_len >= 255(%lu) %.*s\n", (unsigned long)name_len, 255,
&pmgl->chunk.data[j]);
i++;
}
return CHMC_NOERR;
}
int chmc_pmgi_add_entry(struct chmcFile *chm, const char *name, int pmgl_id)
{
struct chmcPmgiChunkNode *pmgi;
struct chmcPmgiChunk *chunk;
struct chmcItspHeader *itsp = &chm->itsp;
UChar *p;
UInt16 *idx;
int name_len;
int outlen;
int should_idx, idx_intlv;
int free;
assert(chm);
// check chunk space for new entry
name_len = strlen(name);
outlen = chmc_encint_len(name_len);
outlen += name_len;
outlen += chmc_encint_len(pmgl_id);
// look for current pmgi chunk, create if doesn't exist
if (!chm->pmgi_last) {
pmgi = chmc_pmgi_create();
if (pmgi)
chmc_pmgi_add(chm, pmgi);
else
chmcerr_set_return(CHMC_ENOMEM, "pmgi chunk: ");
}
else
pmgi = chm->pmgi_last;
do {
chunk = &chm->pmgi_last->chunk;
idx_intlv = 1 + ( 1 << itsp->blockidx_intvl );
should_idx = ( ( chunk->entries_count > 0 )
&& ! ( ( chunk->entries_count + 1 ) % idx_intlv )
? 2 : 0 );
free = sizeof(chunk->data) - pmgi->data_len -
pmgi->index_len - should_idx;
// current(last) chunk doesn't have enough room? force new one
if (outlen + should_idx > free) {
pmgi = chmc_pmgi_create();
if (pmgi)
chmc_pmgi_add(chm, pmgi);
else
chmcerr_set_return(CHMC_ENOMEM, "pmgi chunk: ");
continue;
}
p = (void *)&chunk->data[pmgi->data_len];
if (should_idx) {
idx = (void *)((char *)&chunk->data[CHMC_PMGI_DATA_LEN] - pmgi->index_len);
*idx = (char *)p - (char *)&chunk->data;
}
p += chmc_encint(name_len, p);
memcpy(p, name, name_len);
p += name_len;
p += chmc_encint(pmgl_id, p);
pmgi->data_len += outlen;
pmgi->index_len += should_idx;
chunk->entries_count++;
chunk->header.free_space -= outlen;
break;
} while (1);
return CHMC_NOERR;
}
void chmc_pmgi_add(struct chmcFile *chm, struct chmcPmgiChunkNode *pmgi)
{
struct chmcItspHeader *itsp = &chm->itsp;
assert(chm);
assert(pmgi);
list_add_tail(&pmgi->list, &chm->pmgi_list);
itsp->num_blocks++;
chm->pmgi_last = pmgi;
}
int chmc_write(struct chmcFile *chm)
{
struct chmcItsfHeader *itsf = &chm->itsf;
struct chmcSect0 *sect0 = &chm->sect0;
struct chmcItspHeader *itsp = &chm->itsp;
struct chmcPmglChunkNode *pmgl;
struct chmcPmgiChunkNode *pmgi;
struct list_head *pos;
assert(chm);
chmc_dump("write itsf %d\n", _CHMC_ITSF_V3_LEN);
write(chm->fd, itsf, _CHMC_ITSF_V3_LEN);
chmc_dump("write sect0 %d\n", _CHMC_SECT0_LEN);
write(chm->fd, sect0, _CHMC_SECT0_LEN);
chmc_dump("write itsp %d\n", _CHMC_ITSP_V1_LEN);
write(chm->fd, itsp, _CHMC_ITSP_V1_LEN);
list_for_each(pos, &chm->pmgl_list) {
pmgl = list_entry(pos, struct chmcPmglChunkNode, list);
chmc_dump("write pmgl %d\n", _CHMC_CHUNK_LEN);
write(chm->fd, &pmgl->chunk, _CHMC_CHUNK_LEN);
}
chmc_dump("itsp->num_blocks %d", itsp->num_blocks);
if (itsp->num_blocks > 1) {
list_for_each( pos, &chm->pmgi_list ) {
pmgi = list_entry(pos, struct chmcPmgiChunkNode, list);
chmc_dump("write pmgi %d\n", _CHMC_CHUNK_LEN);
write(chm->fd, &pmgi->chunk, _CHMC_CHUNK_LEN);
}
}
return CHMC_NOERR;
}
int chmc_appendfile(struct chmcFile *chm, const char *filename, void *buf,
size_t size )
{
struct stat statbuf;
int in;
off_t todo, toread;
int rx;
if (stat(filename, &statbuf) < 0)
return errno;
in = open(filename, O_RDONLY | O_BINARY);
if (in >= 0) {
todo = statbuf.st_size;
while (todo) {
toread = size;
if (toread > todo)
toread = todo;
rx = read(in, buf, toread);
if (rx > 0) {
write(chm->fd, buf, rx);
todo -= rx;
}
}
close(in);
}
else
BUG_ON("open %s\n", filename);
return CHMC_NOERR;
}