static VALUE
pack_pack(VALUE ary, VALUE fmt)
{
    static const char nul10[] = "\0\0\0\0\0\0\0\0\0\0";
    static const char spc10[] = "          ";
    const char *p, *pend;
    VALUE res, from, associates = 0;
    char type;
    long items, len, idx, plen;
    const char *ptr;
    int enc_info = 1;           /* 0 - BINARY, 1 - US-ASCII, 2 - UTF-8 */
    int natint;         /* native integer */
    int signed_p, integer_size, bigendian_p;
    StringValue(fmt);
    p = RSTRING_PTR(fmt);
    pend = p + RSTRING_LEN(fmt);
    res = rb_str_buf_new(0);
    items = RARRAY_LEN(ary);
    idx = 0;
    while (p < pend) {
        int explicit_endian = 0;
        if (RSTRING_PTR(fmt) + RSTRING_LEN(fmt) != pend) {
            rb_raise(rb_eRuntimeError, "format string modified");
        }
        type = *p++;           /* get data type */
        natint = 0;
        if (ISSPACE(type)) continue;
        if (type == '#') {
            while ((p < pend) && (*p != '\n')) {
                p++;
            }
            continue;
        }
        {
            static const char natstr[] = "sSiIlL";
            static const char endstr[] = "sSiIlLqQ";
          modifiers:
            switch (*p) {
              case '_':
              case '!':
                if (strchr(natstr, type)) {
                    natint = 1;
                    p++;
                }
                else {
                    rb_raise(rb_eArgError, "'%c' allowed only after types %s", *p, natstr);
                }
                goto modifiers;
              case '<':
              case '>':
                if (!strchr(endstr, type)) {
                    rb_raise(rb_eArgError, "'%c' allowed only after types %s", *p, endstr);
                }
                if (explicit_endian) {
                    rb_raise(rb_eRangeError, "Can't use both '<' and '>'");
                }
                explicit_endian = *p++;
                goto modifiers;
            }
        }
        if (*p == '*') {       /* set data length */
            len = strchr("@Xxu", type) ? 0
                : strchr("PMm", type) ? 1
                : items;
            p++;
        }
        else if (ISDIGIT(*p)) {
            errno = 0;
            len = STRTOUL(p, (char**)&p, 10);
            if (errno) {
                rb_raise(rb_eRangeError, "pack length too big");
            }
        }
        else {
            len = 1;
        }
        switch (type) {
          case 'U':
            /* if encoding is US-ASCII, upgrade to UTF-8 */
            if (enc_info == 1) enc_info = 2;
            break;
          case 'm': case 'M': case 'u':
            /* keep US-ASCII (do nothing) */
            break;
          default:
            /* fall back to BINARY */
            enc_info = 0;
            break;
        }
        switch (type) {
          case 'A': case 'a': case 'Z':
          case 'B': case 'b':
          case 'H': case 'h':
            from = NEXTFROM;
            if (NIL_P(from)) {
                ptr = "";
                plen = 0;
            }
            else {
                StringValue(from);
                ptr = RSTRING_PTR(from);
                plen = RSTRING_LEN(from);
                OBJ_INFECT(res, from);
            }
            if (p[-1] == '*')
                len = plen;
            switch (type) {
              case 'a':                /* arbitrary binary string (null padded)  */
              case 'A':         /* arbitrary binary string (ASCII space padded) */
              case 'Z':         /* null terminated string  */
                if (plen >= len) {
                    rb_str_buf_cat(res, ptr, len);
                    if (p[-1] == '*' && type == 'Z')
                        rb_str_buf_cat(res, nul10, 1);
                }
                else {
                    rb_str_buf_cat(res, ptr, plen);
                    len -= plen;
                    while (len >= 10) {
                        rb_str_buf_cat(res, (type == 'A')?spc10:nul10, 10);
                        len -= 10;
                    }
                    rb_str_buf_cat(res, (type == 'A')?spc10:nul10, len);
                }
                break;
              case 'b':                /* bit string (ascending) */
                {
                    int byte = 0;
                    long i, j = 0;
                    if (len > plen) {
                        j = (len - plen + 1)/2;
                        len = plen;
                    }
                    for (i=0; i++ < len; ptr++) {
                        if (*ptr & 1)
                            byte |= 128;
                        if (i & 7)
                            byte >>= 1;
                        else {
                            char c = byte & 0xff;
                            rb_str_buf_cat(res, &c, 1);
                            byte = 0;
                        }
                    }
                    if (len & 7) {
                        char c;
                        byte >>= 7 - (len & 7);
                        c = byte & 0xff;
                        rb_str_buf_cat(res, &c, 1);
                    }
                    len = j;
                    goto grow;
                }
                break;
              case 'B':                /* bit string (descending) */
                {
                    int byte = 0;
                    long i, j = 0;
                    if (len > plen) {
                        j = (len - plen + 1)/2;
                        len = plen;
                    }
                    for (i=0; i++ < len; ptr++) {
                        byte |= *ptr & 1;
                        if (i & 7)
                            byte <<= 1;
                        else {
                            char c = byte & 0xff;
                            rb_str_buf_cat(res, &c, 1);
                            byte = 0;
                        }
                    }
                    if (len & 7) {
                        char c;
                        byte <<= 7 - (len & 7);
                        c = byte & 0xff;
                        rb_str_buf_cat(res, &c, 1);
                    }
                    len = j;
                    goto grow;
                }
                break;
              case 'h':                /* hex string (low nibble first) */
                {
                    int byte = 0;
                    long i, j = 0;
                    if (len > plen) {
                        j = (len + 1) / 2 - (plen + 1) / 2;
                        len = plen;
                    }
                    for (i=0; i++ < len; ptr++) {
                        if (ISALPHA(*ptr))
                            byte |= (((*ptr & 15) + 9) & 15) << 4;
                        else
                            byte |= (*ptr & 15) << 4;
                        if (i & 1)
                            byte >>= 4;
                        else {
                            char c = byte & 0xff;
                            rb_str_buf_cat(res, &c, 1);
                            byte = 0;
                        }
                    }
                    if (len & 1) {
                        char c = byte & 0xff;
                        rb_str_buf_cat(res, &c, 1);
                    }
                    len = j;
                    goto grow;
                }
                break;
              case 'H':                /* hex string (high nibble first) */
                {
                    int byte = 0;
                    long i, j = 0;
                    if (len > plen) {
                        j = (len + 1) / 2 - (plen + 1) / 2;
                        len = plen;
                    }
                    for (i=0; i++ < len; ptr++) {
                        if (ISALPHA(*ptr))
                            byte |= ((*ptr & 15) + 9) & 15;
                        else
                            byte |= *ptr & 15;
                        if (i & 1)
                            byte <<= 4;
                        else {
                            char c = byte & 0xff;
                            rb_str_buf_cat(res, &c, 1);
                            byte = 0;
                        }
                    }
                    if (len & 1) {
                        char c = byte & 0xff;
                        rb_str_buf_cat(res, &c, 1);
                    }
                    len = j;
                    goto grow;
                }
                break;
            }
            break;
          case 'c':            /* signed char */
          case 'C':            /* unsigned char */
            while (len-- > 0) {
                char c;
                from = NEXTFROM;
                c = (char)num2i32(from);
                rb_str_buf_cat(res, &c, sizeof(char));
            }
            break;
          case 's':            /* signed short */
            signed_p = 1;
            integer_size = NATINT_LEN(short, 2);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'S':            /* unsigned short */
            signed_p = 0;
            integer_size = NATINT_LEN(short, 2);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'i':            /* signed int */
            signed_p = 1;
            integer_size = (int)sizeof(int);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'I':            /* unsigned int */
            signed_p = 0;
            integer_size = (int)sizeof(int);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'l':            /* signed long */
            signed_p = 1;
            integer_size = NATINT_LEN(long, 4);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'L':            /* unsigned long */
            signed_p = 0;
            integer_size = NATINT_LEN(long, 4);
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'q':            /* signed quad (64bit) int */
            signed_p = 1;
            integer_size = 8;
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'Q':            /* unsigned quad (64bit) int */
            signed_p = 0;
            integer_size = 8;
            bigendian_p = BIGENDIAN_P();
            goto pack_integer;
          case 'n':            /* unsigned short (network byte-order)  */
            signed_p = 0;
            integer_size = 2;
            bigendian_p = 1;
            goto pack_integer;
          case 'N':            /* unsigned long (network byte-order) */
            signed_p = 0;
            integer_size = 4;
            bigendian_p = 1;
            goto pack_integer;
          case 'v':            /* unsigned short (VAX byte-order) */
            signed_p = 0;
            integer_size = 2;
            bigendian_p = 0;
            goto pack_integer;
          case 'V':            /* unsigned long (VAX byte-order) */
            signed_p = 0;
            integer_size = 4;
            bigendian_p = 0;
            goto pack_integer;
          pack_integer:
            if (explicit_endian) {
                bigendian_p = explicit_endian == '>';
            }
            switch (integer_size) {
              case SIZEOF_INT16_T:
                while (len-- > 0) {
                    union {
                        int16_t i;
                        char a[sizeof(int16_t)];
                    } v;
                    from = NEXTFROM;
                    v.i = (int16_t)num2i32(from);
                    if (bigendian_p != BIGENDIAN_P()) v.i = swap16(v.i);
                    rb_str_buf_cat(res, v.a, sizeof(int16_t));
                }
                break;
              case SIZEOF_INT32_T:
                while (len-- > 0) {
                    union {
                        int32_t i;
                        char a[sizeof(int32_t)];
                    } v;
                    from = NEXTFROM;
                    v.i = (int32_t)num2i32(from);
                    if (bigendian_p != BIGENDIAN_P()) v.i = swap32(v.i);
                    rb_str_buf_cat(res, v.a, sizeof(int32_t));
                }
                break;
              case SIZEOF_INT64_T:
                while (len-- > 0) {
                    union {
                        int64_t i;
                        char a[sizeof(int64_t)];
                    } v;
                    from = NEXTFROM;
                    v.i = num2i32(from); /* can return 64bit value if SIZEOF_LONG == SIZEOF_INT64_T */
                    if (bigendian_p != BIGENDIAN_P()) v.i = swap64(v.i);
                    rb_str_buf_cat(res, v.a, sizeof(int64_t));
                }
                break;
              default:
                if (integer_size > MAX_INTEGER_PACK_SIZE)
                    rb_bug("unexpected intger size for pack: %d", integer_size);
                while (len-- > 0) {
                    union {
                        unsigned long i[(MAX_INTEGER_PACK_SIZE+SIZEOF_LONG-1)/SIZEOF_LONG];
                        char a[(MAX_INTEGER_PACK_SIZE+SIZEOF_LONG-1)/SIZEOF_LONG*SIZEOF_LONG];
                    } v;
                    int num_longs = (integer_size+SIZEOF_LONG-1)/SIZEOF_LONG;
                    int i;
                    from = NEXTFROM;
                    rb_big_pack(from, v.i, num_longs);
                    if (bigendian_p) {
                        for (i = 0; i < num_longs/2; i++) {
                            unsigned long t = v.i[i];
                            v.i[i] = v.i[num_longs-1-i];
                            v.i[num_longs-1-i] = t;
                        }
                    }
                    if (bigendian_p != BIGENDIAN_P()) {
                        for (i = 0; i < num_longs; i++)
                            v.i[i] = swapl(v.i[i]);
                    }
                    rb_str_buf_cat(res,
                                   bigendian_p ?
                                     v.a + sizeof(long)*num_longs - integer_size :
                                     v.a,
                                   integer_size);
                }
                break;
            }
            break;
          case 'f':            /* single precision float in native format */
          case 'F':            /* ditto */
            while (len-- > 0) {
                float f;
                from = NEXTFROM;
                f = (float)RFLOAT_VALUE(rb_to_float(from));
                rb_str_buf_cat(res, (char*)&f, sizeof(float));
            }
            break;
          case 'e':            /* single precision float in VAX byte-order */
            while (len-- > 0) {
                float f;
                FLOAT_CONVWITH(ftmp);
                from = NEXTFROM;
                f = (float)RFLOAT_VALUE(rb_to_float(from));
                f = HTOVF(f,ftmp);
                rb_str_buf_cat(res, (char*)&f, sizeof(float));
            }
            break;
          case 'E':            /* double precision float in VAX byte-order */
            while (len-- > 0) {
                double d;
                DOUBLE_CONVWITH(dtmp);
                from = NEXTFROM;
                d = RFLOAT_VALUE(rb_to_float(from));
                d = HTOVD(d,dtmp);
                rb_str_buf_cat(res, (char*)&d, sizeof(double));
            }
            break;
          case 'd':            /* double precision float in native format */
          case 'D':            /* ditto */
            while (len-- > 0) {
                double d;
                from = NEXTFROM;
                d = RFLOAT_VALUE(rb_to_float(from));
                rb_str_buf_cat(res, (char*)&d, sizeof(double));
            }
            break;
          case 'g':            /* single precision float in network byte-order */
            while (len-- > 0) {
                float f;
                FLOAT_CONVWITH(ftmp);
                from = NEXTFROM;
                f = (float)RFLOAT_VALUE(rb_to_float(from));
                f = HTONF(f,ftmp);
                rb_str_buf_cat(res, (char*)&f, sizeof(float));
            }
            break;
          case 'G':            /* double precision float in network byte-order */
            while (len-- > 0) {
                double d;
                DOUBLE_CONVWITH(dtmp);
                from = NEXTFROM;
                d = RFLOAT_VALUE(rb_to_float(from));
                d = HTOND(d,dtmp);
                rb_str_buf_cat(res, (char*)&d, sizeof(double));
            }
            break;
          case 'x':            /* null byte */
          grow:
            while (len >= 10) {
                rb_str_buf_cat(res, nul10, 10);
                len -= 10;
            }
            rb_str_buf_cat(res, nul10, len);
            break;
          case 'X':            /* back up byte */
          shrink:
            plen = RSTRING_LEN(res);
            if (plen < len)
                rb_raise(rb_eArgError, "X outside of string");
            rb_str_set_len(res, plen - len);
            break;
          case '@':            /* null fill to absolute position */
            len -= RSTRING_LEN(res);
            if (len > 0) goto grow;
            len = -len;
            if (len > 0) goto shrink;
            break;
          case '%':
            rb_raise(rb_eArgError, "%% is not supported");
            break;
          case 'U':            /* Unicode character */
            while (len-- > 0) {
                SIGNED_VALUE l;
                char buf[8];
                int le;
                from = NEXTFROM;
                from = rb_to_int(from);
                l = NUM2LONG(from);
                if (l < 0) {
                    rb_raise(rb_eRangeError, "pack(U): value out of range");
                }
                le = rb_uv_to_utf8(buf, l);
                rb_str_buf_cat(res, (char*)buf, le);
            }
            break;
          case 'u':            /* uuencoded string */
          case 'm':            /* base64 encoded string */
            from = NEXTFROM;
            StringValue(from);
            ptr = RSTRING_PTR(from);
            plen = RSTRING_LEN(from);
            if (len == 0 && type == 'm') {
                encodes(res, ptr, plen, type, 0);
                ptr += plen;
                break;
            }
            if (len <= 2)
                len = 45;
            else
                len = len / 3 * 3;
            while (plen > 0) {
                long todo;
                if (plen > len)
                    todo = len;
                else
                    todo = plen;
                encodes(res, ptr, todo, type, 1);
                plen -= todo;
                ptr += todo;
            }
            break;
          case 'M':            /* quoted-printable encoded string */
            from = rb_obj_as_string(NEXTFROM);
            if (len <= 1)
                len = 72;
            qpencode(res, from, len);
            break;
          case 'P':            /* pointer to packed byte string */
            from = THISFROM;
            if (!NIL_P(from)) {
                StringValue(from);
                if (RSTRING_LEN(from) < len) {
                    rb_raise(rb_eArgError, "too short buffer for P(%ld for %ld)",
                             RSTRING_LEN(from), len);
                }
            }
            len = 1;
            /* FALL THROUGH */
          case 'p':            /* pointer to string */
            while (len-- > 0) {
                char *t;
                from = NEXTFROM;
                if (NIL_P(from)) {
                    t = 0;
                }
                else {
                    t = StringValuePtr(from);
                }
                if (!associates) {
                    associates = rb_ary_new();
                }
                rb_ary_push(associates, from);
                rb_obj_taint(from);
                rb_str_buf_cat(res, (char*)&t, sizeof(char*));
            }
            break;
          case 'w':            /* BER compressed integer  */
            while (len-- > 0) {
                unsigned long ul;
                VALUE buf = rb_str_new(0, 0);
                char c, *bufs, *bufe;
                from = NEXTFROM;
                if (TYPE(from) == T_BIGNUM) {
                    VALUE big128 = rb_uint2big(128);
                    while (TYPE(from) == T_BIGNUM) {
                        from = rb_big_divmod(from, big128);
                        c = NUM2INT(RARRAY_PTR(from)[1]) | 0x80; /* mod */
                        rb_str_buf_cat(buf, &c, sizeof(char));
                        from = RARRAY_PTR(from)[0]; /* div */
                    }
                }
                {
                    long l = NUM2LONG(from);
                    if (l < 0) {
                        rb_raise(rb_eArgError, "can't compress negative numbers");
                    }
                    ul = l;
                }
                while (ul) {
                    c = (char)(ul & 0x7f) | 0x80;
                    rb_str_buf_cat(buf, &c, sizeof(char));
                    ul >>=  7;
                }
                if (RSTRING_LEN(buf)) {
                    bufs = RSTRING_PTR(buf);
                    bufe = bufs + RSTRING_LEN(buf) - 1;
                    *bufs &= 0x7f; /* clear continue bit */
                    while (bufs < bufe) { /* reverse */
                        c = *bufs;
                        *bufs++ = *bufe;
                        *bufe-- = c;
                    }
                    rb_str_buf_cat(res, RSTRING_PTR(buf), RSTRING_LEN(buf));
                }
                else {
                    c = 0;
                    rb_str_buf_cat(res, &c, sizeof(char));
                }
            }
            break;
          default:
            break;
        }
    }
    if (associates) {
        rb_str_associate(res, associates);
    }
    OBJ_INFECT(res, fmt);
    switch (enc_info) {
      case 1:
        ENCODING_CODERANGE_SET(res, rb_usascii_encindex(), ENC_CODERANGE_7BIT);
        break;
      case 2:
        rb_enc_set_index(res, rb_utf8_encindex());
        break;
      default:
        /* do nothing, keep ASCII-8BIT */
        break;
    }
    return res;
}