2019-01-18 15:54:29 +01:00
|
|
|
#include "image_util.hpp"
|
2019-01-19 18:29:24 +01:00
|
|
|
#include <fstream>
|
|
|
|
|
#include <istream>
|
|
|
|
|
#include <ostream>
|
|
|
|
|
#include <vector>
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
#ifndef DEMO
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
#pragma pack(push, 1)
|
|
|
|
|
struct Head {
|
|
|
|
|
short machine, num_sects;
|
|
|
|
|
int timedata, sym_table, num_syms;
|
|
|
|
|
short opt_size, chars;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Opts {
|
2014-01-31 08:23:00 +13:00
|
|
|
short magic;
|
2019-01-18 17:03:57 +01:00
|
|
|
char major, minor;
|
|
|
|
|
int code_size, data_size, udata_size;
|
|
|
|
|
int entry, code_base, data_base;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
int image_base, sect_align, file_align;
|
|
|
|
|
short major_os, minor_os, major_image, minor_image, major_subsys, minor_subsys;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
int reserved;
|
2019-01-18 17:03:57 +01:00
|
|
|
int image_size, headers_size, checksum;
|
|
|
|
|
|
|
|
|
|
short subsys, dllchars;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
int stack_reserve, stack_commit, heap_reserve, heap_commit, loadflags;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
int dir_entries;
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct DDir {
|
|
|
|
|
int rva, size;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Sect {
|
|
|
|
|
char name[8];
|
|
|
|
|
int virt_size, virt_addr; //in mem
|
|
|
|
|
int data_size, data_addr; //on disk
|
|
|
|
|
int relocs, lines; //file ptrs
|
|
|
|
|
short num_relocs, num_lines;
|
|
|
|
|
int chars;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Rdir {
|
|
|
|
|
int chars, timedata;
|
|
|
|
|
short major, minor, num_names, num_ids;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Rent {
|
|
|
|
|
int id, data;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Rdat {
|
|
|
|
|
int addr, size, cp, zero;
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
#pragma pack(pop)
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Rsrc {
|
2019-01-19 18:29:24 +01:00
|
|
|
int id;
|
|
|
|
|
void* data;
|
|
|
|
|
int data_sz;
|
|
|
|
|
std::vector<Rsrc*> kids;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
Rsrc(int id, Rsrc* p) : id(id), data(0), data_sz(0)
|
|
|
|
|
{
|
|
|
|
|
if (p)
|
|
|
|
|
p->kids.push_back(this);
|
|
|
|
|
// cout<<"res id:"<<dec<<id<<hex<<endl;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
~Rsrc()
|
|
|
|
|
{
|
|
|
|
|
for (; kids.size(); kids.pop_back())
|
|
|
|
|
delete kids.back();
|
2014-01-31 08:23:00 +13:00
|
|
|
delete data;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
struct Section {
|
|
|
|
|
Sect sect;
|
|
|
|
|
char* data;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
Section() : data(0) {}
|
|
|
|
|
~Section()
|
|
|
|
|
{
|
|
|
|
|
delete[] data;
|
|
|
|
|
}
|
2014-01-31 08:23:00 +13:00
|
|
|
};
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static char* stub;
|
|
|
|
|
static int stub_sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static Head* head;
|
|
|
|
|
static int head_sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static Opts* opts;
|
|
|
|
|
static int opts_sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static DDir* ddir;
|
|
|
|
|
static int ddir_sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-19 18:29:24 +01:00
|
|
|
static std::vector<Section*> sections;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static Rsrc* rsrc_root;
|
|
|
|
|
|
|
|
|
|
static const char* img_file;
|
|
|
|
|
|
|
|
|
|
static void openRsrcDir(Section* s, int off, Rsrc* p)
|
|
|
|
|
{
|
|
|
|
|
char* data = (char*)s->data;
|
|
|
|
|
|
|
|
|
|
Rdir* dir = (Rdir*)(data + off);
|
|
|
|
|
Rent* ent = (Rent*)(dir + 1);
|
|
|
|
|
for (int k = 0; k < dir->num_ids; ++ent, ++k) {
|
|
|
|
|
Rsrc* r = new Rsrc(ent->id, p);
|
|
|
|
|
if (ent->data < 0) { //a node - offset is another dir
|
|
|
|
|
openRsrcDir(s, ent->data & 0x7fffffff, r);
|
|
|
|
|
} else { //a leaf
|
|
|
|
|
Rdat* dat = (Rdat*)(data + ent->data);
|
|
|
|
|
// cout<<"dat addr:"<<dat->addr<<" size:"<<dat->size<<endl;
|
|
|
|
|
int sz = dat->size;
|
|
|
|
|
void* src = dat->addr - s->sect.virt_addr + data;
|
|
|
|
|
void* dest = new char[sz];
|
|
|
|
|
memcpy(dest, src, sz);
|
|
|
|
|
r->data = dest;
|
|
|
|
|
r->data_sz = sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static void openRsrcTree(Section* s)
|
|
|
|
|
{
|
|
|
|
|
rsrc_root = new Rsrc(0, 0);
|
|
|
|
|
openRsrcDir(s, 0, rsrc_root);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static int rsrcSize(Rsrc* r)
|
|
|
|
|
{
|
|
|
|
|
if (r->data)
|
|
|
|
|
return (sizeof(Rdat) + r->data_sz + 7) & ~7;
|
|
|
|
|
int sz = sizeof(Rdir);
|
|
|
|
|
for (int k = 0; k < r->kids.size(); ++k) {
|
|
|
|
|
sz += sizeof(Rent) + rsrcSize(r->kids[k]);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
return sz;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static void closeRsrcDir(Section* s, int off, Rsrc* p)
|
|
|
|
|
{
|
|
|
|
|
int t, k;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
char* data = (char*)s->data;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
Rdir* dir = (Rdir*)(data + off);
|
|
|
|
|
memset(dir, 0, sizeof(Rdir));
|
|
|
|
|
dir->num_ids = p->kids.size();
|
|
|
|
|
Rent* ent = (Rent*)(dir + 1);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//to end of dir...
|
2019-01-18 17:03:57 +01:00
|
|
|
off += sizeof(Rdir) + sizeof(Rent) * p->kids.size();
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
t = off;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//write entries
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < p->kids.size(); ++ent, ++k) {
|
|
|
|
|
Rsrc* r = p->kids[k];
|
|
|
|
|
ent->id = r->id;
|
|
|
|
|
ent->data = t;
|
|
|
|
|
if (!r->data)
|
|
|
|
|
ent->data |= 0x80000000;
|
|
|
|
|
t += rsrcSize(r);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
t = off;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//write kids...
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < p->kids.size(); ++k) {
|
|
|
|
|
Rsrc* r = p->kids[k];
|
|
|
|
|
if (!r->data) {
|
|
|
|
|
closeRsrcDir(s, t, r);
|
|
|
|
|
} else {
|
|
|
|
|
Rdat* dat = (Rdat*)(data + t);
|
|
|
|
|
dat->addr = s->sect.virt_addr + t + sizeof(Rdat);
|
|
|
|
|
dat->size = r->data_sz;
|
|
|
|
|
dat->zero = dat->cp = 0;
|
|
|
|
|
memcpy(data + t + sizeof(Rdat), r->data, r->data_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
2019-01-18 17:03:57 +01:00
|
|
|
t += rsrcSize(r);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static int fileAlign(int n)
|
|
|
|
|
{
|
|
|
|
|
return (n + (opts->file_align - 1)) & ~(opts->file_align - 1);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static int sectAlign(int n)
|
|
|
|
|
{
|
|
|
|
|
return (n + (opts->sect_align - 1)) & ~(opts->sect_align - 1);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static void closeRsrcTree(Section* s)
|
|
|
|
|
{
|
|
|
|
|
int virt_sz = rsrcSize(rsrc_root);
|
|
|
|
|
int data_sz = fileAlign(virt_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
int virt_delta = sectAlign(virt_sz) - sectAlign(s->sect.virt_size);
|
|
|
|
|
int data_delta = fileAlign(virt_sz) - fileAlign(s->sect.virt_size);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (int k = 0; k < sections.size(); ++k) {
|
|
|
|
|
Section* t = sections[k];
|
|
|
|
|
if (t->sect.virt_addr > s->sect.virt_addr) {
|
|
|
|
|
t->sect.virt_addr += virt_delta;
|
|
|
|
|
if (!strcmp(t->sect.name, ".reloc")) {
|
|
|
|
|
ddir[5].rva = t->sect.virt_addr;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
2019-01-18 17:03:57 +01:00
|
|
|
if (t->sect.data_addr > s->sect.data_addr) {
|
|
|
|
|
t->sect.data_addr += data_delta;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
ddir[2].size = virt_sz;
|
|
|
|
|
opts->image_size += virt_delta;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
s->sect.virt_size = virt_sz;
|
|
|
|
|
s->sect.data_size = data_sz;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
delete[] s->data;
|
2019-01-18 17:03:57 +01:00
|
|
|
s->data = new char[data_sz];
|
|
|
|
|
closeRsrcDir(s, 0, rsrc_root);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
delete rsrc_root;
|
2019-01-18 17:03:57 +01:00
|
|
|
rsrc_root = 0;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static Rsrc* findRsrc(int id, Rsrc* p)
|
|
|
|
|
{
|
|
|
|
|
for (int k = 0; k < p->kids.size(); ++k) {
|
|
|
|
|
if (p->kids[k]->id == id)
|
|
|
|
|
return p->kids[k];
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
static Rsrc* findRsrc(int type, int id, int lang)
|
|
|
|
|
{
|
|
|
|
|
Rsrc* r = findRsrc(type, rsrc_root);
|
|
|
|
|
if (!r)
|
|
|
|
|
return 0;
|
|
|
|
|
r = findRsrc(id, r);
|
|
|
|
|
if (!r)
|
|
|
|
|
return 0;
|
|
|
|
|
return findRsrc(lang, r);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-19 18:29:24 +01:00
|
|
|
static void loadImage(std::istream& in)
|
2019-01-18 17:03:57 +01:00
|
|
|
{
|
2014-01-31 08:23:00 +13:00
|
|
|
int k;
|
|
|
|
|
|
|
|
|
|
//read stub
|
2019-01-18 17:03:57 +01:00
|
|
|
in.seekg(0x3c);
|
|
|
|
|
in.read((char*)&stub_sz, 4);
|
|
|
|
|
stub_sz += 4;
|
|
|
|
|
stub = new char[stub_sz];
|
|
|
|
|
in.seekg(0);
|
|
|
|
|
in.read(stub, stub_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//read head
|
2019-01-18 17:03:57 +01:00
|
|
|
head = new Head;
|
|
|
|
|
head_sz = sizeof(Head);
|
|
|
|
|
in.read((char*)head, head_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//read opts
|
2019-01-18 17:03:57 +01:00
|
|
|
opts = new Opts;
|
|
|
|
|
opts_sz = sizeof(Opts);
|
|
|
|
|
in.read((char*)opts, opts_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//read data dirs
|
2019-01-18 17:03:57 +01:00
|
|
|
ddir_sz = opts->dir_entries * sizeof(DDir);
|
|
|
|
|
ddir = (DDir*)new char[ddir_sz];
|
|
|
|
|
in.read((char*)ddir, ddir_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
|
|
|
|
//read sects...
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < head->num_sects; ++k) {
|
|
|
|
|
Section* s = new Section;
|
|
|
|
|
in.read((char*)&s->sect, sizeof(Sect));
|
|
|
|
|
sections.push_back(s);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < head->num_sects; ++k) {
|
|
|
|
|
Section* s = sections[k];
|
|
|
|
|
if (!s->sect.data_addr)
|
|
|
|
|
continue;
|
|
|
|
|
int data_sz = s->sect.data_size;
|
|
|
|
|
s->data = new char[data_sz]; //char[s->sect.virt_size];
|
2014-01-31 08:23:00 +13:00
|
|
|
//memset( s->data,0,s->sect.virt_size );
|
2019-01-18 17:03:57 +01:00
|
|
|
in.seekg(s->sect.data_addr);
|
|
|
|
|
in.read(s->data, data_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-19 18:29:24 +01:00
|
|
|
static void saveImage(std::ostream& out)
|
2019-01-18 17:03:57 +01:00
|
|
|
{
|
2014-01-31 08:23:00 +13:00
|
|
|
int k;
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
out.write((char*)stub, stub_sz);
|
|
|
|
|
out.write((char*)head, head_sz);
|
|
|
|
|
out.write((char*)opts, opts_sz);
|
|
|
|
|
out.write((char*)ddir, ddir_sz);
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < head->num_sects; ++k) {
|
|
|
|
|
Section* s = sections[k];
|
|
|
|
|
out.write((char*)&s->sect, sizeof(Sect));
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (k = 0; k < head->num_sects; ++k) {
|
|
|
|
|
Section* s = sections[k];
|
|
|
|
|
if (!s->sect.data_addr)
|
|
|
|
|
continue;
|
2014-01-31 08:23:00 +13:00
|
|
|
//assumes sect data is in order!!!!!
|
2019-01-18 17:03:57 +01:00
|
|
|
while (out.tellp() < s->sect.data_addr)
|
|
|
|
|
out.put((char)0xbb);
|
|
|
|
|
out.seekp(s->sect.data_addr);
|
|
|
|
|
out.write(s->data, s->sect.data_size);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/********************** PUBLIC STUFF ***********************/
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
bool openImage(const char* img)
|
|
|
|
|
{
|
|
|
|
|
img_file = img;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-19 18:29:24 +01:00
|
|
|
std::fstream in(img_file, std::ios_base::binary | std::ios_base::in);
|
2019-01-18 17:03:57 +01:00
|
|
|
loadImage(in);
|
2014-01-31 08:23:00 +13:00
|
|
|
in.close();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
bool makeExe(int entry)
|
|
|
|
|
{
|
|
|
|
|
if (!img_file)
|
|
|
|
|
return false;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
head->chars |= 0x0002; //executable
|
|
|
|
|
head->chars &= ~0x2000; //not Dll
|
|
|
|
|
opts->entry = entry;
|
|
|
|
|
// opts->image_base=0x400000; //have to deal to fix-ups to do this properly.
|
2014-01-31 08:23:00 +13:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
bool replaceRsrc(int type, int id, int lang, void* data, int data_sz)
|
|
|
|
|
{
|
|
|
|
|
if (!img_file)
|
|
|
|
|
return false;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (int k = 0; k < sections.size(); ++k) {
|
|
|
|
|
Section* s = sections[k];
|
|
|
|
|
if (strcmp(s->sect.name, ".rsrc"))
|
|
|
|
|
continue;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
openRsrcTree(s);
|
|
|
|
|
if (Rsrc* r = findRsrc(type, id, lang)) {
|
2014-01-31 08:23:00 +13:00
|
|
|
delete[] r->data;
|
2019-01-18 17:03:57 +01:00
|
|
|
r->data_sz = data_sz;
|
|
|
|
|
r->data = new char[data_sz];
|
|
|
|
|
memcpy(r->data, data, data_sz);
|
|
|
|
|
closeRsrcTree(s);
|
2014-01-31 08:23:00 +13:00
|
|
|
return true;
|
|
|
|
|
}
|
2019-01-18 17:03:57 +01:00
|
|
|
closeRsrcTree(s);
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
void closeImage()
|
|
|
|
|
{
|
|
|
|
|
if (!img_file)
|
|
|
|
|
return;
|
2014-01-31 08:23:00 +13:00
|
|
|
|
2019-01-19 18:29:24 +01:00
|
|
|
std::fstream out(img_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);
|
2019-01-18 17:03:57 +01:00
|
|
|
saveImage(out);
|
2014-01-31 08:23:00 +13:00
|
|
|
out.close();
|
|
|
|
|
|
2019-01-18 17:03:57 +01:00
|
|
|
for (; sections.size(); sections.pop_back())
|
|
|
|
|
delete sections.back();
|
2014-01-31 08:23:00 +13:00
|
|
|
delete[] ddir;
|
|
|
|
|
delete opts;
|
|
|
|
|
delete head;
|
|
|
|
|
delete[] stub;
|
2019-01-18 17:03:57 +01:00
|
|
|
img_file = 0;
|
2014-01-31 08:23:00 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|