Differences

This shows you the differences between two versions of the page.

inbox:macosx.lion [2013/07/14 09:19] (current)
Line 1: Line 1:
 +====== Stuff with MacOSX Lion ======
 +| →[[doc:howto:buildroot.exigence|Procedure on Debian]] | →[[doc:howto:buildroot.exigence.bsd|Procedure on ~BSD]] | **→[[doc:howto:buildroot.exigence.macosx|Procedure on Mac OS]]** |
 +
 +----
 +:!: Please MERGE this page and [[/easy.build.macosx]] into this page: **[[doc/howto/buildroot.exigence.macosx]]**. Also consult the [[https://forum.openwrt.org/viewtopic.php?id=34676|OpenWrt Forum: Build OpenWrt on Mac OS X 10.7 Lion]]
 +
 +----
 +
 +
 +===== Getting the mktplinkfw  =====
 +
 +  * (Revision 29681, 22.1 KB (checked in by juhosg, 7 weeks ago)) working in MacOSX Lion Xcode 4.4 Developer review in 64bit and not need md5.c/md5.h (because md5.c not support 64bit)
 +
 +
 +[[doc/howto/generic.flashing.tftp#getting.tftp.to.work.in.macosx.lion]]
 +
 +I don't think I can merge my changes to the trunk, original file is from
 +
 +https://dev.openwrt.org/browser/trunk/tools/firmware-utils/src/mktplinkfw.c  (Revision 29681)
 +
 +If you need 32bit OSX (or dual 64 and 32) one just change it in the project settings
 +
 +Create new CLI (command line) application, then replace all the main.m with this.
 +Then compile, then the output is at the DerivedData/Project/Debug directory
 +
 +Summary of changes
 +
 +1) replace all getmd5 with CC_MD5 (x2)
 +
 +2) comment out md5.h
 +
 +3) Add CommonDigest.h
 +
 +4) All the "struct getmd5" comment out
 +
 +
 +I don't know if functions other than "inspect" will work properly
 +
 +<code>
 +//
 +//  main.m
 +// 
 +//
 +//  Created by on Saturday,2/25/12.
 +//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
 +//
 +
 +#import <Foundation/Foundation.h>
 +#import <CommonCrypto/CommonDigest.h>
 +/*
 + * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
 + *
 + * This tool was based on:
 + *  TP-Link WR941 V2 firmware checksum fixing tool.
 + *  Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
 + *
 + * This program is free software; you can redistribute it and/or modify it
 + * under the terms of the GNU General Public License version 2 as published
 + * by the Free Software Foundation.
 + *
 + */
 +
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <stdint.h>
 +#include <string.h>
 +#include <unistd.h>    /* for unlink() */
 +#include <libgen.h>
 +#include <getopt.h>    /* for getopt() */
 +#include <stdarg.h>
 +#include <errno.h>
 +#include <sys/stat.h>
 +
 +#include <arpa/inet.h>
 +#include <netinet/in.h>
 +
 +//#include "md5.h"
 +
 +#define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
 +
 +#define HEADER_VERSION_V1 0x01000000
 +#define HWID_TL_MR3020_V1 0x30200001
 +#define HWID_TL_MR3220_V1 0x32200001
 +#define HWID_TL_MR3420_V1 0x34200001
 +#define HWID_TL_WA701N_V1 0x07010001
 +#define HWID_TL_WA901ND_V1 0x09010001
 +#define HWID_TL_WA901ND_V2 0x09010002
 +#define HWID_TL_WR703N_V1 0x07030101
 +#define HWID_TL_WR741ND_V1 0x07410001
 +#define HWID_TL_WR741ND_V4 0x07410004
 +#define HWID_TL_WR740N_V1 0x07400001
 +#define HWID_TL_WR740N_V3 0x07400003
 +#define HWID_TL_WR743ND_V1 0x07430001
 +#define HWID_TL_WR841N_V1_5 0x08410002
 +#define HWID_TL_WR841ND_V3 0x08410003
 +#define HWID_TL_WR841ND_V5 0x08410005
 +#define HWID_TL_WR841ND_V7 0x08410007
 +#define HWID_TL_WR941ND_V2 0x09410002
 +#define HWID_TL_WR941ND_V4 0x09410004
 +#define HWID_TL_WR1043ND_V1 0x10430001
 +#define HWID_TL_WR2543N_V1 0x25430001
 +
 +#define MD5SUM_LEN 16
 +
 +struct file_info {
 +char *file_name; /* name of the file */
 +uint32_t file_size; /* length of the file */
 +};
 +
 +struct fw_header {
 +uint32_t version; /* header version */
 +char vendor_name[24];
 +char fw_version[36];
 +uint32_t hw_id; /* hardware id */
 +uint32_t hw_rev; /* hardware revision */
 +uint32_t unk1;
 +uint8_t md5sum1[MD5SUM_LEN];
 +uint32_t unk2;
 +uint8_t md5sum2[MD5SUM_LEN];
 +uint32_t unk3;
 +uint32_t kernel_la; /* kernel load address */
 +uint32_t kernel_ep; /* kernel entry point */
 +uint32_t fw_length; /* total length of the firmware */
 +uint32_t kernel_ofs; /* kernel data offset */
 +uint32_t kernel_len; /* kernel data length */
 +uint32_t rootfs_ofs; /* rootfs data offset */
 +uint32_t rootfs_len; /* rootfs data length */
 +uint32_t boot_ofs; /* bootloader data offset */
 +uint32_t boot_len; /* bootloader data length */
 +uint8_t pad[360];
 +} __attribute__ ((packed));
 +
 +struct flash_layout {
 +char *id;
 +uint32_t fw_max_len;
 +uint32_t kernel_la;
 +uint32_t kernel_ep;
 +uint32_t rootfs_ofs;
 +};
 +
 +struct board_info {
 +char *id;
 +uint32_t hw_id;
 +uint32_t hw_rev;
 +char *layout_id;
 +};
 +
 +/*
 + * Globals
 + */
 +static char *ofname;
 +static char *progname;
 +static char *vendor = "TP-LINK Technologies";
 +static char *version = "ver. 1.0";
 +
 +static char *board_id;
 +static struct board_info *board;
 +static char *layout_id;
 +static struct flash_layout *layout;
 +static char *opt_hw_id;
 +static uint32_t hw_id;
 +static char *opt_hw_rev;
 +static uint32_t hw_rev;
 +static struct file_info kernel_info;
 +static uint32_t kernel_la = 0;
 +static uint32_t kernel_ep = 0;
 +static uint32_t kernel_len = 0;
 +static struct file_info rootfs_info;
 +static uint32_t rootfs_ofs = 0;
 +static uint32_t rootfs_align;
 +static struct file_info boot_info;
 +static int combined;
 +static int strip_padding;
 +static int add_jffs2_eof;
 +static unsigned char jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
 +
 +static struct file_info inspect_info;
 +static int extract = 0;
 +
 +char md5salt_normal[MD5SUM_LEN] = {
 + 0xdc, 0xd7, 0x3a, 0xa5, 0xc3, 0x95, 0x98, 0xfb,
 + 0xdd, 0xf9, 0xe7, 0xf4, 0x0e, 0xae, 0x47, 0x38,
 +};
 +
 +char md5salt_boot[MD5SUM_LEN] = {
 + 0x8c, 0xef, 0x33, 0x5b, 0xd5, 0xc5, 0xce, 0xfa,
 + 0xa7, 0x9c, 0x28, 0xda, 0xb2, 0xe9, 0x0f, 0x42,
 +};
 +
 +static struct flash_layout layouts[] = {
 + {
 + .id = "4M",
 + .fw_max_len = 0x3c0000,
 + .kernel_la = 0x80060000,
 + .kernel_ep = 0x80060000,
 + .rootfs_ofs = 0x140000,
 + }, {
 + .id = "4Mlzma",
 + .fw_max_len = 0x3c0000,
 + .kernel_la = 0x80060000,
 + .kernel_ep = 0x80060000,
 + .rootfs_ofs = 0x100000,
 + }, {
 + .id = "8M",
 + .fw_max_len = 0x7c0000,
 + .kernel_la = 0x80060000,
 + .kernel_ep = 0x80060000,
 + .rootfs_ofs = 0x140000,
 + }, {
 + .id = "8Mlzma",
 + .fw_max_len = 0x7c0000,
 + .kernel_la = 0x80060000,
 + .kernel_ep = 0x80060000,
 + .rootfs_ofs = 0x100000,
 + }, {
 + /* terminating entry */
 + }
 +};
 +
 +static struct board_info boards[] = {
 + {
 + .id = "TL-MR3020v1",
 + .hw_id = HWID_TL_MR3020_V1,
 + .hw_rev = 1,
 + .layout_id = "4Mlzma",
 + }, {
 + .id = "TL-MR3220v1",
 + .hw_id = HWID_TL_MR3220_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-MR3420v1",
 + .hw_id = HWID_TL_MR3420_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WA701Nv1",
 + .hw_id = HWID_TL_WA701N_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WA901NDv1",
 + .hw_id = HWID_TL_WA901ND_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id            = "TL-WA901NDv2",
 + .hw_id          = HWID_TL_WA901ND_V2,
 + .hw_rev        = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR741NDv1",
 + .hw_id = HWID_TL_WR741ND_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR741NDv4",
 + .hw_id = HWID_TL_WR741ND_V4,
 + .hw_rev = 1,
 + .layout_id = "4Mlzma",
 + }, {
 + .id = "TL-WR740Nv1",
 + .hw_id = HWID_TL_WR740N_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR740Nv3",
 + .hw_id = HWID_TL_WR740N_V3,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR743NDv1",
 + .hw_id = HWID_TL_WR743ND_V1,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR841Nv1.5",
 + .hw_id = HWID_TL_WR841N_V1_5,
 + .hw_rev = 2,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR841NDv3",
 + .hw_id = HWID_TL_WR841ND_V3,
 + .hw_rev = 3,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR841NDv5",
 + .hw_id = HWID_TL_WR841ND_V5,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR841NDv7",
 + .hw_id = HWID_TL_WR841ND_V7,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR941NDv2",
 + .hw_id = HWID_TL_WR941ND_V2,
 + .hw_rev = 2,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR941NDv4",
 + .hw_id = HWID_TL_WR941ND_V4,
 + .hw_rev = 1,
 + .layout_id = "4M",
 + }, {
 + .id = "TL-WR1043NDv1",
 + .hw_id = HWID_TL_WR1043ND_V1,
 + .hw_rev = 1,
 + .layout_id = "8M",
 + }, {
 + .id = "TL-WR2543Nv1",
 + .hw_id = HWID_TL_WR2543N_V1,
 + .hw_rev = 1,
 + .layout_id = "8Mlzma",
 + }, {
 + .id = "TL-WR703Nv1",
 + .hw_id = HWID_TL_WR703N_V1,
 + .hw_rev = 1,
 + .layout_id = "4Mlzma",
 + }, {
 + /* terminating entry */
 + }
 +};
 +
 +/*
 + * Message macros
 + */
 +#define ERR(fmt, ...) do { \
 +fflush(0); \
 +fprintf(stderr, "[%s] *** error: " fmt "\n", \
 +progname, ## __VA_ARGS__ ); \
 +} while (0)
 +
 +#define ERRS(fmt, ...) do { \
 +int save = errno; \
 +fflush(0); \
 +fprintf(stderr, "[%s] *** error: " fmt "\n", \
 +progname, ## __VA_ARGS__, strerror(save)); \
 +} while (0)
 +
 +#define DBG(fmt, ...) do { \
 +fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
 +} while (0)
 +
 +static struct board_info *find_board(char *id)
 +{
 + struct board_info *ret;
 + struct board_info *board;
 +   
 + ret = NULL;
 + for (board = boards; board->id != NULL; board++){
 + if (strcasecmp(id, board->id) == 0) {
 + ret = board;
 + break;
 + }
 + };
 +   
 + return ret;
 +}
 +
 +static struct board_info *find_board_by_hwid(uint32_t hw_id)
 +{
 + struct board_info *board;
 +   
 + for (board = boards; board->id != NULL; board++) {
 + if (hw_id == board->hw_id)
 + return board;
 + };
 +   
 + return NULL;
 +}
 +
 +static struct flash_layout *find_layout(char *id)
 +{
 + struct flash_layout *ret;
 + struct flash_layout *l;
 +   
 + ret = NULL;
 + for (l = layouts; l->id != NULL; l++){
 + if (strcasecmp(id, l->id) == 0) {
 + ret = l;
 + break;
 + }
 + };
 +   
 + return ret;
 +}
 +
 +static void usage(int status)
 +{
 + FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
 + struct board_info *board;
 +   
 + fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
 + fprintf(stream,
 +            "\n"
 +            "Options:\n"
 +            "  -B <board>      create image for the board specified with <board>\n"
 +            "  -c              use combined kernel image\n"
 +            "  -E <ep>        overwrite kernel entry point with <ep> (hexval prefixed with 0x)\n"
 +            "  -L <la>        overwrite kernel load address with <la> (hexval prefixed with 0x)\n"
 +            "  -H <hwid>      use hardware id specified with <hwid>\n"
 +            "  -F <id>        use flash layout specified with <id>\n"
 +            "  -k <file>      read kernel image from the file <file>\n"
 +            "  -r <file>      read rootfs image from the file <file>\n"
 +            "  -a <align>      align the rootfs start on an <align> bytes boundary\n"
 +            "  -R <offset>    overwrite rootfs offset with <offset> (hexval prefixed with 0x)\n"
 +            "  -o <file>      write output to the file <file>\n"
 +            "  -s              strip padding from the end of the image\n"
 +            "  -j              add jffs2 end-of-filesystem markers\n"
 +            "  -N <vendor>    set image vendor to <vendor>\n"
 +            "  -V <version>    set image version to <version>\n"
 +            "  -i <file>      inspect given firmware file <file>\n"
 +            "  -x              extract kernel and rootfs while inspecting (requires -i)\n"
 +            "  -h              show this screen\n"
 +            );
 +   
 + exit(status);
 +}
 +
 +//static int get_md5(char *data, int size, char *md5)
 +//{
 +// MD5_CTX ctx;
 +//   
 +// MD5_Init(&ctx);
 +// MD5_Update(&ctx, data, size);
 +// MD5_Final(md5, &ctx);
 +//}
 +
 +static int get_file_stat(struct file_info *fdata)
 +{
 + struct stat st;
 + int res;
 +   
 + if (fdata->file_name == NULL)
 + return 0;
 +   
 + res = stat(fdata->file_name, &st);
 + if (res){
 + ERRS("stat failed on %s", fdata->file_name);
 + return res;
 + }
 +   
 + fdata->file_size = st.st_size;
 + return 0;
 +}
 +
 +static int read_to_buf(struct file_info *fdata, char *buf)
 +{
 + FILE *f;
 + int ret = EXIT_FAILURE;
 +   
 + f = fopen(fdata->file_name, "r");
 + if (f == NULL) {
 + ERRS("could not open \"%s\" for reading", fdata->file_name);
 + goto out;
 + }
 +   
 + errno = 0;
 + fread(buf, fdata->file_size, 1, f);
 + if (errno != 0) {
 + ERRS("unable to read from file \"%s\"", fdata->file_name);
 + goto out_close;
 + }
 +   
 + ret = EXIT_SUCCESS;
 +   
 +out_close:
 + fclose(f);
 +    out:
 + return ret;
 +}
 +
 +static int check_options(void)
 +{
 + int ret;
 +   
 + if (inspect_info.file_name) {
 + ret = get_file_stat(&inspect_info);
 + if (ret)
 + return ret;
 +       
 + return 0;
 + } else if (extract) {
 + ERR("no firmware for inspection specified");
 + return -1;
 + }
 +   
 + if (board_id == NULL && opt_hw_id == NULL) {
 + ERR("either board or hardware id must be specified");
 + return -1;
 + }
 +   
 + if (board_id) {
 + board = find_board(board_id);
 + if (board == NULL) {
 + ERR("unknown/unsupported board id \"%s\"", board_id);
 + return -1;
 + }
 + if (layout_id == NULL)
 + layout_id = board->layout_id;
 +       
 + hw_id = board->hw_id;
 + hw_rev = board->hw_rev;
 + } else {
 + if (layout_id == NULL) {
 + ERR("flash layout is not specified");
 + return -1;
 + }
 + hw_id = strtoul(opt_hw_id, NULL, 0);
 +       
 + if (opt_hw_rev)
 + hw_rev = strtoul(opt_hw_rev, NULL, 0);
 + else
 + hw_rev = 1;
 + }
 +   
 + layout = find_layout(layout_id);
 + if (layout == NULL) {
 + ERR("unknown flash layout \"%s\"", layout_id);
 + return -1;
 + }
 +   
 + if (!kernel_la)
 + kernel_la = layout->kernel_la;
 + if (!kernel_ep)
 + kernel_ep = layout->kernel_ep;
 + if (!rootfs_ofs)
 + rootfs_ofs = layout->rootfs_ofs;
 +   
 + if (kernel_info.file_name == NULL) {
 + ERR("no kernel image specified");
 + return -1;
 + }
 +   
 + ret = get_file_stat(&kernel_info);
 + if (ret)
 + return ret;
 +   
 + kernel_len = kernel_info.file_size;
 +   
 + if (combined) {
 + if (kernel_info.file_size >
 +     layout->fw_max_len - sizeof(struct fw_header)) {
 + ERR("kernel image is too big");
 + return -1;
 + }
 + } else {
 + if (rootfs_info.file_name == NULL) {
 + ERR("no rootfs image specified");
 + return -1;
 + }
 +       
 + ret = get_file_stat(&rootfs_info);
 + if (ret)
 + return ret;
 +       
 + if (rootfs_align) {
 + kernel_len += sizeof(struct fw_header);
 + kernel_len = ALIGN(kernel_len, rootfs_align);
 + kernel_len -= sizeof(struct fw_header);
 +           
 + DBG("kernel length aligned to %u", kernel_len);
 +           
 + if (kernel_len + rootfs_info.file_size >
 +     layout->fw_max_len - sizeof(struct fw_header)) {
 + ERR("images are too big");
 + return -1;
 + }
 + } else {
 + if (kernel_info.file_size >
 +     rootfs_ofs - sizeof(struct fw_header)) {
 + ERR("kernel image is too big");
 + return -1;
 + }
 +           
 + if (rootfs_info.file_size >
 +     (layout->fw_max_len - rootfs_ofs)) {
 + ERR("rootfs image is too big");
 + return -1;
 + }
 + }
 + }
 +   
 + if (ofname == NULL) {
 + ERR("no output file specified");
 + return -1;
 + }
 +   
 + return 0;
 +}
 +
 +static void fill_header(char *buf, int len)
 +{
 + struct fw_header *hdr = (struct fw_header *)buf;
 +   
 + memset(hdr, 0, sizeof(struct fw_header));
 +   
 + hdr->version = htonl(HEADER_VERSION_V1);
 + strncpy(hdr->vendor_name, vendor, sizeof(hdr->vendor_name));
 + strncpy(hdr->fw_version, version, sizeof(hdr->fw_version));
 + hdr->hw_id = htonl(hw_id);
 + hdr->hw_rev = htonl(hw_rev);
 +   
 + if (boot_info.file_size == 0)
 + memcpy(hdr->md5sum1, md5salt_normal, sizeof(hdr->md5sum1));
 + else
 + memcpy(hdr->md5sum1, md5salt_boot, sizeof(hdr->md5sum1));
 +   
 + hdr->kernel_la = htonl(kernel_la);
 + hdr->kernel_ep = htonl(kernel_ep);
 + hdr->fw_length = htonl(layout->fw_max_len);
 + hdr->kernel_ofs = htonl(sizeof(struct fw_header));
 + hdr->kernel_len = htonl(kernel_len);
 + if (!combined) {
 + hdr->rootfs_ofs = htonl(rootfs_ofs);
 + hdr->rootfs_len = htonl(rootfs_info.file_size);
 + }
 +   
 + CC_MD5(buf, len, hdr->md5sum1);
 +}
 +
 +static int pad_jffs2(char *buf, int currlen)
 +{
 + int len;
 + uint32_t pad_mask;
 +   
 + len = currlen;
 + pad_mask = (64 * 1024);
 + while ((len < layout->fw_max_len) && (pad_mask != 0)) {
 + uint32_t mask;
 + int i;
 +       
 + for (i = 10; i < 32; i++) {
 + mask = 1 << i;
 + if (pad_mask & mask)
 + break;
 + }
 +       
 + len = ALIGN(len, mask);
 +       
 + for (i = 10; i < 32; i++) {
 + mask = 1 << i;
 + if ((len & (mask - 1)) == 0)
 + pad_mask &= ~mask;
 + }
 +       
 + for (i = 0; i < sizeof(jffs2_eof_mark); i++)
 + buf[len + i] = jffs2_eof_mark[i];
 +       
 + len += sizeof(jffs2_eof_mark);
 + }
 +   
 + return len;
 +}
 +
 +static int write_fw(char *data, int len)
 +{
 + FILE *f;
 + int ret = EXIT_FAILURE;
 +   
 + f = fopen(ofname, "w");
 + if (f == NULL) {
 + ERRS("could not open \"%s\" for writing", ofname);
 + goto out;
 + }
 +   
 + errno = 0;
 + fwrite(data, len, 1, f);
 + if (errno) {
 + ERRS("unable to write output file");
 + goto out_flush;
 + }
 +   
 + DBG("firmware file \"%s\" completed", ofname);
 +   
 + ret = EXIT_SUCCESS;
 +   
 +out_flush:
 + fflush(f);
 + fclose(f);
 + if (ret != EXIT_SUCCESS) {
 + unlink(ofname);
 + }
 +    out:
 + return ret;
 +}
 +
 +static int build_fw(void)
 +{
 + int buflen;
 + char *buf;
 + char *p;
 + int ret = EXIT_FAILURE;
 + int writelen = 0;
 +   
 + buflen = layout->fw_max_len;
 +   
 + buf = malloc(buflen);
 + if (!buf) {
 + ERR("no memory for buffer\n");
 + goto out;
 + }
 +   
 + memset(buf, 0xff, buflen);
 + p = buf + sizeof(struct fw_header);
 + ret = read_to_buf(&kernel_info, p);
 + if (ret)
 + goto out_free_buf;
 +   
 + writelen = sizeof(struct fw_header) + kernel_len;
 +   
 + if (!combined) {
 + if (rootfs_align)
 + p = buf + writelen;
 + else
 + p = buf + rootfs_ofs;
 +       
 + ret = read_to_buf(&rootfs_info, p);
 + if (ret)
 + goto out_free_buf;
 +       
 + if (rootfs_align)
 + writelen += rootfs_info.file_size;
 + else
 + writelen = rootfs_ofs + rootfs_info.file_size;
 +       
 + if (add_jffs2_eof)
 + writelen = pad_jffs2(buf, writelen);
 + }
 +   
 + if (!strip_padding)
 + writelen = buflen;
 +   
 + fill_header(buf, writelen);
 + ret = write_fw(buf, writelen);
 + if (ret)
 + goto out_free_buf;
 +   
 + ret = EXIT_SUCCESS;
 +   
 +out_free_buf:
 + free(buf);
 +    out:
 + return ret;
 +}
 +
 +/* Helper functions to inspect_fw() representing different output formats */
 +static inline void inspect_fw_pstr(char *label, char *str)
 +{
 + printf("%-23s: %s\n", label, str);
 +}
 +
 +static inline void inspect_fw_phex(char *label, uint32_t val)
 +{
 + printf("%-23s: 0x%08x\n", label, val);
 +}
 +
 +static inline void inspect_fw_phexpost(char *label,
 +                                      uint32_t val, char *post)
 +{
 + printf("%-23s: 0x%08x (%s)\n", label, val, post);
 +}
 +
 +static inline void inspect_fw_phexdef(char *label,
 +                                      uint32_t val, uint32_t defval)
 +{
 + printf("%-23s: 0x%08x                  ", label, val);
 +   
 + if (val == defval)
 + printf("(== OpenWrt default)\n");
 + else
 + printf("(OpenWrt default: 0x%08x)\n", defval);
 +}
 +
 +static inline void inspect_fw_phexexp(char *label,
 +                                      uint32_t val, uint32_t expval)
 +{
 + printf("%-23s: 0x%08x ", label, val);
 +   
 + if (val == expval)
 + printf("(ok)\n");
 + else
 + printf("(expected: 0x%08x)\n", expval);
 +}
 +
 +static inline void inspect_fw_phexdec(char *label, uint32_t val)
 +{
 + printf("%-23s: 0x%08x / %8u bytes\n", label, val, val);
 +}
 +
 +static inline void inspect_fw_phexdecdef(char *label,
 +                                        uint32_t val, uint32_t defval)
 +{
 + printf("%-23s: 0x%08x / %8u bytes ", label, val, val);
 +   
 + if (val == defval)
 + printf("(== OpenWrt default)\n");
 + else
 + printf("(OpenWrt default: 0x%08x)\n", defval);
 +}
 +
 +static inline void inspect_fw_pmd5sum(char *label, uint8_t *val, char *text)
 +{
 + int i;
 +   
 + printf("%-23s:", label);
 + for (i=0; i<MD5SUM_LEN; i++)
 + printf(" %02x", val[i]);
 + printf(" %s\n", text);
 +}
 +
 +static int inspect_fw(void)
 +{
 + char *buf;
 + struct fw_header *hdr;
 + uint8_t md5sum[MD5SUM_LEN];
 + struct board_info *board;
 + int ret = EXIT_FAILURE;
 +   
 + buf = malloc(inspect_info.file_size);
 + if (!buf) {
 + ERR("no memory for buffer!\n");
 + goto out;
 + }
 +   
 + ret = read_to_buf(&inspect_info, buf);
 + if (ret)
 + goto out_free_buf;
 + hdr = (struct fw_header *)buf;
 +   
 + inspect_fw_pstr("File name", inspect_info.file_name);
 + inspect_fw_phexdec("File size", inspect_info.file_size);
 +   
 + if (ntohl(hdr->version) != HEADER_VERSION_V1) {
 + ERR("file does not seem to have V1 header!\n");
 + goto out_free_buf;
 + }
 +   
 + inspect_fw_phexdec("Version 1 Header size", sizeof(struct fw_header));
 +   
 + if (ntohl(hdr->unk1) != 0)
 + inspect_fw_phexdec("Unknown value 1", hdr->unk1);
 +   
 + memcpy(md5sum, hdr->md5sum1, sizeof(md5sum));
 + if (ntohl(hdr->boot_len) == 0)
 + memcpy(hdr->md5sum1, md5salt_normal, sizeof(md5sum));
 + else
 + memcpy(hdr->md5sum1, md5salt_boot, sizeof(md5sum));
 + CC_MD5(buf, inspect_info.file_size, hdr->md5sum1);
 +   
 + if (memcmp(md5sum, hdr->md5sum1, sizeof(md5sum))) {
 + inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(*ERROR*)");
 + inspect_fw_pmd5sum("          --> expected", hdr->md5sum1, "");
 + } else {
 + inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(ok)");
 + }
 + if (ntohl(hdr->unk2) != 0)
 + inspect_fw_phexdec("Unknown value 2", hdr->unk2);
 + inspect_fw_pmd5sum("Header MD5Sum2", hdr->md5sum2,
 +                   "(purpose yet unknown, unchecked here)");
 + if (ntohl(hdr->unk3) != 0)
 + inspect_fw_phexdec("Unknown value 3", hdr->unk3);
 +   
 + printf("\n");
 +   
 + inspect_fw_pstr("Vendor name", hdr->vendor_name);
 + inspect_fw_pstr("Firmware version", hdr->fw_version);
 + board = find_board_by_hwid(ntohl(hdr->hw_id));
 + if (board) {
 + layout = find_layout(board->layout_id);
 + inspect_fw_phexpost("Hardware ID",
 +                     ntohl(hdr->hw_id), board->id);
 + inspect_fw_phexexp("Hardware Revision",
 +                   ntohl(hdr->hw_rev), board->hw_rev);
 + } else {
 + inspect_fw_phexpost("Hardware ID",
 +                     ntohl(hdr->hw_id), "unknown");
 + inspect_fw_phex("Hardware Revision",
 +                 ntohl(hdr->hw_rev));
 + }
 +   
 + printf("\n");
 +   
 + inspect_fw_phexdec("Kernel data offset",
 +                   ntohl(hdr->kernel_ofs));
 + inspect_fw_phexdec("Kernel data length",
 +                   ntohl(hdr->kernel_len));
 + if (board) {
 + inspect_fw_phexdef("Kernel load address",
 +                   ntohl(hdr->kernel_la),
 +                   layout ? layout->kernel_la : 0xffffffff);
 + inspect_fw_phexdef("Kernel entry point",
 +                   ntohl(hdr->kernel_ep),
 +                   layout ? layout->kernel_ep : 0xffffffff);
 + inspect_fw_phexdecdef("Rootfs data offset",
 +                       ntohl(hdr->rootfs_ofs),
 +                       layout ? layout->rootfs_ofs : 0xffffffff);
 + } else {
 + inspect_fw_phex("Kernel load address",
 +                 ntohl(hdr->kernel_la));
 + inspect_fw_phex("Kernel entry point",
 +                 ntohl(hdr->kernel_ep));
 + inspect_fw_phexdec("Rootfs data offset",
 +                   ntohl(hdr->rootfs_ofs));
 + }
 + inspect_fw_phexdec("Rootfs data length",
 +                   ntohl(hdr->rootfs_len));
 + inspect_fw_phexdec("Boot loader data offset",
 +                   ntohl(hdr->boot_ofs));
 + inspect_fw_phexdec("Boot loader data length",
 +                   ntohl(hdr->boot_len));
 + inspect_fw_phexdec("Total firmware length",
 +                   ntohl(hdr->fw_length));
 +   
 + if (extract) {
 + FILE *fp;
 + char *filename;
 +       
 + printf("\n");
 +       
 + filename = malloc(strlen(inspect_info.file_name) + 8);
 + sprintf(filename, "%s-kernel", inspect_info.file_name);
 + printf("Extracting kernel to \"%s\"...\n", filename);
 + fp = fopen(filename, "w");
 + if (fp) {
 + if (!fwrite(buf + ntohl(hdr->kernel_ofs),
 +             ntohl(hdr->kernel_len), 1, fp)) {
 + ERR("error in fwrite(): %s", strerror(errno));
 + }
 + fclose(fp);
 + } else {
 + ERR("error in fopen(): %s", strerror(errno));
 + }
 + free(filename);
 +       
 + filename = malloc(strlen(inspect_info.file_name) + 8);
 + sprintf(filename, "%s-rootfs", inspect_info.file_name);
 + printf("Extracting rootfs to \"%s\"...\n", filename);
 + fp = fopen(filename, "w");
 + if (fp) {
 + if (!fwrite(buf + ntohl(hdr->rootfs_ofs),
 +             ntohl(hdr->rootfs_len), 1, fp)) {
 + ERR("error in fwrite(): %s", strerror(errno));
 + }
 + fclose(fp);
 + } else {
 + ERR("error in fopen(): %s", strerror(errno));
 + }
 + free(filename);
 + }
 +   
 +out_free_buf:
 + free(buf);
 +    out:
 + return ret;
 +}
 +
 +int main(int argc, char *argv[])
 +{
 + int ret = EXIT_FAILURE;
 + int err;
 +   
 + FILE *outfile;
 +   
 + progname = basename(argv[0]);
 +   
 + while ( 1 ) {
 + int c;
 +       
 + c = getopt(argc, argv, "a:B:H:E:F:L:V:N:W:ci:k:r:R:o:xhsj");
 + if (c == -1)
 + break;
 +       
 + switch (c) {
 +            case 'a':
 +                sscanf(optarg, "0x%x", &rootfs_align);
 +                break;
 +            case 'B':
 +                board_id = optarg;
 +                break;
 +            case 'H':
 +                opt_hw_id = optarg;
 +                break;
 +            case 'E':
 +                sscanf(optarg, "0x%x", &kernel_ep);
 +                break;
 +            case 'F':
 +                layout_id = optarg;
 +                break;
 +            case 'W':
 +                opt_hw_rev = optarg;
 +                break;
 +            case 'L':
 +                sscanf(optarg, "0x%x", &kernel_la);
 +                break;
 +            case 'V':
 +                version = optarg;
 +                break;
 +            case 'N':
 +                vendor = optarg;
 +                break;
 +            case 'c':
 +                combined++;
 +                break;
 +            case 'k':
 +                kernel_info.file_name = optarg;
 +                break;
 +            case 'r':
 +                rootfs_info.file_name = optarg;
 +                break;
 +            case 'R':
 +                sscanf(optarg, "0x%x", &rootfs_ofs);
 +                break;
 +            case 'o':
 +                ofname = optarg;
 +                break;
 +            case 's':
 +                strip_padding = 1;
 +                break;
 +            case 'i':
 +                inspect_info.file_name = optarg;
 +                break;
 +            case 'j':
 +                add_jffs2_eof = 1;
 +                break;
 +            case 'x':
 +                extract = 1;
 +                break;
 +            case 'h':
 +                usage(EXIT_SUCCESS);
 +                break;
 +            default:
 +                usage(EXIT_FAILURE);
 +                break;
 + }
 + }
 +   
 + ret = check_options();
 + if (ret)
 + goto out;
 +   
 + if (!inspect_info.file_name)
 + ret = build_fw();
 + else
 + ret = inspect_fw();
 +   
 +    out:
 + return ret;
 +}
 +</code>
 +
 +===== Some outputs from the above program for 6 firmwares =====
 +
 +
 +Original FW 111103 (cat the mtd1 and mtd2 into /tmp, then tftp it over and merge them with ie: copy /b in DOS)
 +<code>
 +File name              : 
 +File size              : 0x003c0000 /  3932160 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Header MD5Sum1        : c0 72 33 5f b8 63 23 93 1e 7c 25 8a 8f 49 a7 82 (ok)
 +Header MD5Sum2        : 7d 1a 19 f4 57 b2 98 ed 75 03 02 19 64 87 48 b9 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : TP-LINK Technologies
 +Firmware version      : ver. 1.0
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000da2db /  893659 bytes
 +Kernel load address    : 0x80002000
 +Kernel entry point    : 0x801d6980
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x002c0000 /  2883584 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +MD5 (kernel) = f47785e1d34a94cb457310052be68fa9
 +MD5 (rootfs) = 724b260768caa752ad20ed467d4c78ef
 +
 +893659 Feb 25 04:08 kernel
 +2883584 Feb 25 04:08 rootfs
 +
 +LZMA (magic number 5D 00 00 80) at kernel file
 +from http://www.mjmwired.net/kernel/Documentation/x86/boot.txt
 +http://www.devttys0.com/2011/06/mystery-file-system/
 +
 +Using 7zip for windows, it shows LZMA:25
 +
 +Kernels between the 111103 and 111129 are the same binary wise, only the complied date "texts" (two places) are different
 +
 +</code>
 +
 +FW from Tplink site 111129
 +http://www.tp-link.cn/pages/download-detail.asp?d=696
 +
 +<code>
 +File name              : 111129/mr11uv1.bin
 +File size              : 0x003c0000 /  3932160 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Unknown value 1        : 0x6813fc20 / 1746140192 bytes
 +Header MD5Sum1        : eb 99 bb c1 73 fd 15 04 9b 52 bd 69 ee 5f b7 1b (ok)
 +Header MD5Sum2        : c9 82 b9 5f 5c c2 30 df f3 5b bd 24 0b cb 52 46 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : TP-LINK Technologies
 +Firmware version      : ver. 1.0
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000da300 /  893696 bytes
 +Kernel load address    : 0x80002000
 +Kernel entry point    : 0x801d6980
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x002c0000 /  2883584 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +893696 Feb 25 04:09 mr11uv1.bin-kernel
 +2883584 Feb 25 04:09 mr11uv1.bin-rootfs
 +
 +MD5 (mr11uv1.bin-kernel) = 5cf9b55ed98e31fd934c1100093206be
 +MD5 (mr11uv1.bin-rootfs) = a79be973a0dd3ec09861de94d4bb8ef9
 +
 +LZMA (magic number 5D 00 00 80) at kernel file
 +
 +Using 7zip for windows, it shows LZMA:25
 +
 +</code>
 +
 +
 +The difference between the 11103 and 11129 are (TODO)
 +
 +9 files changed in /etc
 +4 files changed in /lib
 +5 files changed in /usr
 +29 files changed in /web
 +
 +
 +From http://downloads.openwrt.org/snapshots/trunk/ar71xx/
 +
 +All are Feb 19, 2012 snapshots
 +
 +openwrt-ar71xx-generic-tl-mr11u-v1-jffs2-factory.bin
 +<code>
 +File name              : openwrt-ar71xx-generic-tl-mr11u-v1-jffs2-factory.bin
 +File size              : 0x003c0000 /  3932160 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Header MD5Sum1        : 84 66 3f 25 5a b6 13 54 1e 1a e4 83 98 2e 0b 23 (ok)
 +Header MD5Sum2        : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : OpenWrt
 +Firmware version      : r30639
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000efe00 /  982528 bytes
 +Kernel load address    : 0x80060000
 +Kernel entry point    : 0x80060000
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x00220004 /  2228228 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +(magic number 6D 00) at kernel file
 +
 +Using 7zip for windows, it shows LZMA:23
 +
 +Runs Linux 3.2.5
 +</code>
 +
 +openwrt-ar71xx-generic-tl-mr11u-v1-jffs2-sysupgrade.bin
 +<code>
 +File name              : openwrt-ar71xx-generic-tl-mr11u-v1-jffs2-sysupgrade.bin
 +File size              : 0x00320004 /  3276804 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Header MD5Sum1        : 21 bb 88 24 cd 12 2f b6 92 1b 22 0d db 54 50 e8 (ok)
 +Header MD5Sum2        : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : OpenWrt
 +Firmware version      : r30639
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000efe00 /  982528 bytes
 +Kernel load address    : 0x80060000
 +Kernel entry point    : 0x80060000
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x00220004 /  2228228 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +</code>
 +
 +openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin
 +
 +<code>
 +File name              : openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin
 +File size              : 0x003c0000 /  3932160 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Header MD5Sum1        : 23 eb 64 02 83 76 f2 58 07 59 9d 92 72 7b 35 ab (ok)
 +Header MD5Sum2        : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : OpenWrt
 +Firmware version      : r30639
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000e3d5c /  933212 bytes
 +Kernel load address    : 0x80060000
 +Kernel entry point    : 0x80060000
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x0016b6c2 /  1488578 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +
 +MD5 (openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin-kernel) = e670abedba1561d0b9508748839bdb08
 +MD5 (openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin-rootfs) = fc8b1d348d7ee655c7c373e8920e6eea
 +
 +(same md5 for kernel as below)
 +
 +933212 Feb 25 03:57 openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin-kernel
 +1488578 Feb 25 03:57 openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-factory.bin-rootfs
 +
 +Wrong rootfs offset in the header... should be 0xE3F5C
 +
 +</code>
 +
 +openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin
 +<code>
 +File name              : openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin
 +File size              : 0x00250004 /  2424836 bytes
 +Version 1 Header size  : 0x00000200 /      512 bytes
 +Header MD5Sum1        : 47 bf 73 ae dd d0 a5 08 c8 cb a9 21 be e0 21 5a (ok)
 +Header MD5Sum2        : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (purpose yet unknown, unchecked here)
 +
 +Vendor name            : OpenWrt
 +Firmware version      : r30639
 +Hardware ID            : 0x00110101 (unknown)
 +Hardware Revision      : 0x00000001
 +
 +Kernel data offset    : 0x00000200 /      512 bytes
 +Kernel data length    : 0x000e3d5c /  933212 bytes
 +Kernel load address    : 0x80060000
 +Kernel entry point    : 0x80060000
 +Rootfs data offset    : 0x00100000 /  1048576 bytes
 +Rootfs data length    : 0x0016b6c2 /  1488578 bytes
 +Boot loader data offset: 0x00000000 /        0 bytes
 +Boot loader data length: 0x00000000 /        0 bytes
 +Total firmware length  : 0x003c0000 /  3932160 bytes
 +
 +Gives error when extracting (why?)
 + *** error: error in fwrite(): Bad address
 +
 +MD5 (openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin-kernel) = e670abedba1561d0b9508748839bdb08
 +MD5 (openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin-rootfs) = 3e5ac7044a1ce39a52a77cf17718a17d
 +
 +933212 Feb 25 03:59 openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin-kernel
 +1380352 Feb 25 03:59 openwrt-ar71xx-generic-tl-mr11u-v1-squashfs-sysupgrade.bin-rootfs
 +
 +(smaller sized rootfs? corrupted?)
 +
 +Wrong rootfs offset in the header... should be 0xE3F5C
 +</code>
 +TODO
 +
 +===== To Extract the rootfs of factory image =====
 +
 +from https://forum.openwrt.org/viewtopic.php?pid=151276
 +
 +Use Xcode checkout to get this
 +
 +http://code.google.com/p/firmware-mod-kit/source/browse/trunk/trunk/src/others/squashfs-4.0-lzma/
 +
 +then copy the squashfs-4.0-lzma directory from "/others/" out to usb stick, then bootup ie: Parallels Desktop 7 for Mac with the BackBox 2.01 linux ISO image in text mode, then mount the usb stick inside, then do "make" to create the unsquashfs-lzma executable
 +
 +to extract the files, you would need ext2 or the root of the ramdisk is fine too, just make a dir ie: /extracted
 +
 +use unsquashfs-lzma rootfs to extract it
 +
 +use mksquashfs-lzma pathtoread/ outputFile to recreate image
 +
 +then just paste the result into the offset 100000(IIRC) (you will see the squashfs header), then fix md5, then flash via firmware upgrade
 +
 +===== Making it work with other "unsupported" modems with factory image =====
 +
 +==== patching httpd to remove ERROR commands ====
 +
 +
 +ie: AT+CFUN=1
 +
 +TODO
 +
 +the pppd command run is
 +<code>
 +/dev/ttyUSB0 115200 connect chat -V -E -f /etc/ppp/conn-script disconnect chat -
 +V -E -f /etc/ppp/disconn-script defaultroute noaccomp nopcomp lcp-echo-failure 5
 + lcp-echo-interval 30 httpd-pid x user_len x passwd_len x special-flag 100 m
 +ru 1480 mtu 1480 usepeerdns noipdefault refuse-chap refuse-mschap refuse-mschap-
 +v2 refuse-eap ipcp-accept-local ipcp-accept-remote unit 0
 +</code>
 +
 +==== Result ====
 +
 +
 +Telus/Koodo is fine (can use pap), 
 +
 +Bell Mobility will need some fixing (keeps failing authentication)
 +
 +remove the "user_len xx passwd_len xx" section,  (don't know why but it fixed it)
 +then add your own user "x" password "x"
 +
 +Also you have to fix the httpd-pid to match the real pid in the argument, else the pppd will terminate because httpd does not "detect" pppd is online, even though it is.
 +
 +
 +Average time from cold boot to "online" : 1 min 15 seconds
 +
 +TODO

Back to top

inbox/macosx.lion.txt · Last modified: 2013/07/14 09:19 (external edit)