Advertisement
Guest User

Spore decoder by Ymgve and Rick

a guest
Oct 31st, 2011
689
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 22.49 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. import os, sys, optparse, re, time, zlib, struct, random
  4. from PIL import Image
  5.  
  6. version = "v1.0 (2008-06-27)"
  7.  
  8. class Decoder(object):
  9.     def __init__(self, data):
  10.         self.data = data
  11.         self.hash = 0x811c9dc5
  12.         self.next_pos = 0x0b400
  13.  
  14.     def __iter__(self):
  15.         return self
  16.  
  17.     def next(self):
  18.             byte = 0    #this look terrible, but it's optimized
  19.             for i in xrange(8):
  20.                 n = self.next_pos
  21.                 d = self.data[n]
  22.                 e = (self.hash * 0x1000193) & 0xffffffff
  23.                 self.hash = e ^ ((n & 7) | (d & 0xf8))
  24.                 e = ((d&1) << 7) ^ ((self.hash & 0x8000) >> 8)
  25.                 byte = (byte >> 1) | e
  26.                 self.next_pos = (n >> 1) ^ (0x0b400 & -(n & 1))
  27.                    
  28.                 if (self.next_pos == 0x0b400):
  29.                     raise StopIteration
  30.  
  31.             return byte
  32.  
  33.     def get_data(self):
  34.         return ''.join(map(chr, self))
  35.  
  36. def decode_creature(file, ferror=False):
  37.     """decode a creature located in <file>. if <ferror>, include filename
  38.            in error messages.
  39.       returns (creature_image, creature_data)
  40.       raises IOError if it failes to read the image"""
  41.  
  42.     if file != '-' and not os.path.exists(file):
  43.         err("%r doesn't exist." % file)
  44.  
  45.     im = Image.open(file if file != '-' else sys.stdin)
  46.     im.load() #force it to actually read the image
  47.    
  48.     if im.size != (128, 128):
  49.         err("invalid creature file %s(should be 128x128, is %dx%d)" %
  50.                 ((fin(file)+' ' if ferror else "",) + im.size))
  51.    
  52.     l = []
  53.     for y in xrange(128):
  54.         for x in xrange(128):
  55.             (r,g,b,a) = im.getpixel((x,y))
  56.             l.extend((b,g,r,a))
  57.  
  58.     stream = Decoder(l)
  59.     data = stream.get_data()
  60.     length = struct.unpack("<L", data[4:8])[0]
  61.     try:
  62.         inflate = zlib.decompress(data[8:8+length])
  63.     except zlib.error:
  64.         err("invalid creature file %s(zlib decompression failed)" %
  65.                 (fin(file)+' ' if ferror else ''))
  66.     csum = struct.unpack("<Q", data[12+length:20+length])[0]
  67.     if crc64(data[8:8+length]) != csum:
  68.         alert("warning: invalid crc64   ")
  69.     debug("%4dB -> %5dB (%.1f%%) " % (
  70.             length, len(inflate), 100-float(length)/len(inflate)*100))
  71.  
  72.     return (im, inflate)
  73.  
  74. CRC64_Table = (
  75.    0x0000000000000000,0x42F0E1EBA9EA3693,0x85E1C3D753D46D26,0xC711223CFA3E5BB5,
  76.    0x493366450E42ECDF,0x0BC387AEA7A8DA4C,0xCCD2A5925D9681F9,0x8E224479F47CB76A,
  77.    0x9266CC8A1C85D9BE,0xD0962D61B56FEF2D,0x17870F5D4F51B498,0x5577EEB6E6BB820B,
  78.    0xDB55AACF12C73561,0x99A54B24BB2D03F2,0x5EB4691841135847,0x1C4488F3E8F96ED4,
  79.    0x663D78FF90E185EF,0x24CD9914390BB37C,0xE3DCBB28C335E8C9,0xA12C5AC36ADFDE5A,
  80.    0x2F0E1EBA9EA36930,0x6DFEFF5137495FA3,0xAAEFDD6DCD770416,0xE81F3C86649D3285,
  81.    0xF45BB4758C645C51,0xB6AB559E258E6AC2,0x71BA77A2DFB03177,0x334A9649765A07E4,
  82.    0xBD68D2308226B08E,0xFF9833DB2BCC861D,0x388911E7D1F2DDA8,0x7A79F00C7818EB3B,
  83.    0xCC7AF1FF21C30BDE,0x8E8A101488293D4D,0x499B3228721766F8,0x0B6BD3C3DBFD506B,
  84.    0x854997BA2F81E701,0xC7B97651866BD192,0x00A8546D7C558A27,0x4258B586D5BFBCB4,
  85.    0x5E1C3D753D46D260,0x1CECDC9E94ACE4F3,0xDBFDFEA26E92BF46,0x990D1F49C77889D5,
  86.    0x172F5B3033043EBF,0x55DFBADB9AEE082C,0x92CE98E760D05399,0xD03E790CC93A650A,
  87.    0xAA478900B1228E31,0xE8B768EB18C8B8A2,0x2FA64AD7E2F6E317,0x6D56AB3C4B1CD584,
  88.    0xE374EF45BF6062EE,0xA1840EAE168A547D,0x66952C92ECB40FC8,0x2465CD79455E395B,
  89.    0x3821458AADA7578F,0x7AD1A461044D611C,0xBDC0865DFE733AA9,0xFF3067B657990C3A,
  90.    0x711223CFA3E5BB50,0x33E2C2240A0F8DC3,0xF4F3E018F031D676,0xB60301F359DBE0E5,
  91.    0xDA050215EA6C212F,0x98F5E3FE438617BC,0x5FE4C1C2B9B84C09,0x1D14202910527A9A,
  92.    0x93366450E42ECDF0,0xD1C685BB4DC4FB63,0x16D7A787B7FAA0D6,0x5427466C1E109645,
  93.    0x4863CE9FF6E9F891,0x0A932F745F03CE02,0xCD820D48A53D95B7,0x8F72ECA30CD7A324,
  94.    0x0150A8DAF8AB144E,0x43A04931514122DD,0x84B16B0DAB7F7968,0xC6418AE602954FFB,
  95.    0xBC387AEA7A8DA4C0,0xFEC89B01D3679253,0x39D9B93D2959C9E6,0x7B2958D680B3FF75,
  96.    0xF50B1CAF74CF481F,0xB7FBFD44DD257E8C,0x70EADF78271B2539,0x321A3E938EF113AA,
  97.    0x2E5EB66066087D7E,0x6CAE578BCFE24BED,0xABBF75B735DC1058,0xE94F945C9C3626CB,
  98.    0x676DD025684A91A1,0x259D31CEC1A0A732,0xE28C13F23B9EFC87,0xA07CF2199274CA14,
  99.    0x167FF3EACBAF2AF1,0x548F120162451C62,0x939E303D987B47D7,0xD16ED1D631917144,
  100.    0x5F4C95AFC5EDC62E,0x1DBC74446C07F0BD,0xDAAD56789639AB08,0x985DB7933FD39D9B,
  101.    0x84193F60D72AF34F,0xC6E9DE8B7EC0C5DC,0x01F8FCB784FE9E69,0x43081D5C2D14A8FA,
  102.    0xCD2A5925D9681F90,0x8FDAB8CE70822903,0x48CB9AF28ABC72B6,0x0A3B7B1923564425,
  103.    0x70428B155B4EAF1E,0x32B26AFEF2A4998D,0xF5A348C2089AC238,0xB753A929A170F4AB,
  104.    0x3971ED50550C43C1,0x7B810CBBFCE67552,0xBC902E8706D82EE7,0xFE60CF6CAF321874,
  105.    0xE224479F47CB76A0,0xA0D4A674EE214033,0x67C58448141F1B86,0x253565A3BDF52D15,
  106.    0xAB1721DA49899A7F,0xE9E7C031E063ACEC,0x2EF6E20D1A5DF759,0x6C0603E6B3B7C1CA,
  107.    0xF6FAE5C07D3274CD,0xB40A042BD4D8425E,0x731B26172EE619EB,0x31EBC7FC870C2F78,
  108.    0xBFC9838573709812,0xFD39626EDA9AAE81,0x3A28405220A4F534,0x78D8A1B9894EC3A7,
  109.    0x649C294A61B7AD73,0x266CC8A1C85D9BE0,0xE17DEA9D3263C055,0xA38D0B769B89F6C6,
  110.    0x2DAF4F0F6FF541AC,0x6F5FAEE4C61F773F,0xA84E8CD83C212C8A,0xEABE6D3395CB1A19,
  111.    0x90C79D3FEDD3F122,0xD2377CD44439C7B1,0x15265EE8BE079C04,0x57D6BF0317EDAA97,
  112.    0xD9F4FB7AE3911DFD,0x9B041A914A7B2B6E,0x5C1538ADB04570DB,0x1EE5D94619AF4648,
  113.    0x02A151B5F156289C,0x4051B05E58BC1E0F,0x87409262A28245BA,0xC5B073890B687329,
  114.    0x4B9237F0FF14C443,0x0962D61B56FEF2D0,0xCE73F427ACC0A965,0x8C8315CC052A9FF6,
  115.    0x3A80143F5CF17F13,0x7870F5D4F51B4980,0xBF61D7E80F251235,0xFD913603A6CF24A6,
  116.    0x73B3727A52B393CC,0x31439391FB59A55F,0xF652B1AD0167FEEA,0xB4A25046A88DC879,
  117.    0xA8E6D8B54074A6AD,0xEA16395EE99E903E,0x2D071B6213A0CB8B,0x6FF7FA89BA4AFD18,
  118.    0xE1D5BEF04E364A72,0xA3255F1BE7DC7CE1,0x64347D271DE22754,0x26C49CCCB40811C7,
  119.    0x5CBD6CC0CC10FAFC,0x1E4D8D2B65FACC6F,0xD95CAF179FC497DA,0x9BAC4EFC362EA149,
  120.    0x158E0A85C2521623,0x577EEB6E6BB820B0,0x906FC95291867B05,0xD29F28B9386C4D96,
  121.    0xCEDBA04AD0952342,0x8C2B41A1797F15D1,0x4B3A639D83414E64,0x09CA82762AAB78F7,
  122.    0x87E8C60FDED7CF9D,0xC51827E4773DF90E,0x020905D88D03A2BB,0x40F9E43324E99428,
  123.    0x2CFFE7D5975E55E2,0x6E0F063E3EB46371,0xA91E2402C48A38C4,0xEBEEC5E96D600E57,
  124.    0x65CC8190991CB93D,0x273C607B30F68FAE,0xE02D4247CAC8D41B,0xA2DDA3AC6322E288,
  125.    0xBE992B5F8BDB8C5C,0xFC69CAB42231BACF,0x3B78E888D80FE17A,0x7988096371E5D7E9,
  126.    0xF7AA4D1A85996083,0xB55AACF12C735610,0x724B8ECDD64D0DA5,0x30BB6F267FA73B36,
  127.    0x4AC29F2A07BFD00D,0x08327EC1AE55E69E,0xCF235CFD546BBD2B,0x8DD3BD16FD818BB8,
  128.    0x03F1F96F09FD3CD2,0x41011884A0170A41,0x86103AB85A2951F4,0xC4E0DB53F3C36767,
  129.    0xD8A453A01B3A09B3,0x9A54B24BB2D03F20,0x5D45907748EE6495,0x1FB5719CE1045206,
  130.    0x919735E51578E56C,0xD367D40EBC92D3FF,0x1476F63246AC884A,0x568617D9EF46BED9,
  131.    0xE085162AB69D5E3C,0xA275F7C11F7768AF,0x6564D5FDE549331A,0x279434164CA30589,
  132.    0xA9B6706FB8DFB2E3,0xEB46918411358470,0x2C57B3B8EB0BDFC5,0x6EA7525342E1E956,
  133.    0x72E3DAA0AA188782,0x30133B4B03F2B111,0xF7021977F9CCEAA4,0xB5F2F89C5026DC37,
  134.    0x3BD0BCE5A45A6B5D,0x79205D0E0DB05DCE,0xBE317F32F78E067B,0xFCC19ED95E6430E8,
  135.    0x86B86ED5267CDBD3,0xC4488F3E8F96ED40,0x0359AD0275A8B6F5,0x41A94CE9DC428066,
  136.    0xCF8B0890283E370C,0x8D7BE97B81D4019F,0x4A6ACB477BEA5A2A,0x089A2AACD2006CB9,
  137.    0x14DEA25F3AF9026D,0x562E43B4931334FE,0x913F6188692D6F4B,0xD3CF8063C0C759D8,
  138.    0x5DEDC41A34BBEEB2,0x1F1D25F19D51D821,0xD80C07CD676F8394,0x9AFCE626CE85B507)
  139.  
  140. def crc64(data):
  141.     crc = 0xffffffffffffffff
  142.     for byte in data:
  143.         byte = (crc >> 56) ^ ord(byte)
  144.         crc = (crc << 8) & 0xffffffffffffffff ^ CRC64_Table[byte]
  145.     return crc
  146.  
  147. class Encoder(object):
  148.     def __init__(self, data=0):
  149.         if data:
  150.             self.data = data
  151.         else:
  152.             self.data = [128]*(128*128*4)
  153.         self.hash = 0x811c9dc5
  154.         self.next_pos = 0x0b400
  155.         self.maxsize = 0x00010000
  156.         self.unknown = 0x00000001
  157.         self.mask = 0xfffffff8
  158.         self.invmask = 0x00000007
  159.         self.start_pos = 0x0000b400
  160.  
  161.  
  162.     def put_byte(self, byte):
  163.         # again, savagely optimized
  164.         for i in xrange(8):
  165.             bit = (byte >> i) & 1
  166.            
  167.             n = self.next_pos
  168.             d = self.data[n]
  169.             e = (self.hash * 0x1000193) & 0xffffffff
  170.             self.hash = e ^ ((n&7) | (d&0xf8))
  171.             e = bit ^ ((self.hash & 0x8000) >> 15)
  172.             self.data[n] = (d & 254) | e
  173.             self.next_pos = (n >> 1) ^ (0xb400 & -(n & 1))
  174.  
  175.     def put_data(self, data):
  176.         for byte in data:
  177.             self.put_byte(ord(byte))
  178.  
  179. def encode_data(data, im, out):
  180.     comp = zlib.compress(data, 9)
  181.     csum = struct.pack("<Q", crc64(comp))
  182.     odat = "\x16\x0d\x45\x02" + struct.pack("<L", len(comp)) + \
  183.             comp + "\x16\x0d\x45\x02" + csum
  184.     odat = odat.ljust(8192, "\x00")
  185.  
  186.     if len(odat) > 8192: #This is too large to fit in the image
  187.         err("compressed data is %.1fKB > 8KB, doesn't fit" %
  188.                 (len(odat)/1024.))
  189.  
  190.     if im:
  191.         if im.size != (128, 128):
  192.             im = im.resize((128,128), resample=Image.ANTIALIAS)
  193.         if im.mode != 'RGBA':
  194.             im = im.convert(mode='RGBA')
  195.         id = im.getdata()
  196.         pix = []
  197.         for x in xrange(128*128):
  198.             r, g, b, a = id[x]
  199.             pix.extend((b, g, r, a))
  200.         enc = Encoder(pix)
  201.     else:
  202.         enc = Encoder()
  203.     enc.put_data(odat)
  204.  
  205.     im_str = ''.join(chr(enc.data[i+2])+ #BGRA -> RGBA
  206.                      chr(enc.data[i+1])+
  207.                      chr(enc.data[i+0])+
  208.                      chr(enc.data[i+3])
  209.                      for i in xrange(0, len(enc.data), 4))
  210.  
  211.     im = Image.fromstring("RGBA", (128,128), im_str)
  212.  
  213.     try:
  214.         im.save(out if out != '-' else sys.stdout, "PNG")
  215.     except IOError:
  216.         err("unable to output to %s" % fon(out))
  217.  
  218. '''Header format:
  219. #    contents   name        description
  220. 1    "spore"
  221. 2    %04d       version     version? appears to always be 5
  222. 3    %08x       tid         type id (0x2b978c46 means extracted xml)
  223. 4    %08x       gid         group id  (0x40626200 is the default package)
  224. 5    %08x       id          instance id
  225. 6    %08x       mid         machine id? (constant for a user)
  226. 7    %016x      cid         creature id ((int64)(-1) if offline)
  227. 8    %016x      time        timestamp in seconds since AD 1 (?)
  228. 9    %02x                    length of user name
  229. 10   string     uname       user name
  230. 11   %016llx    uid         user id
  231. 12   %02x                    length of creature name
  232. 13   string     name        creature name
  233. 14   %03x                    length of creature description
  234. 15   string     desc        creature description
  235. 16   %02x                    length of creature tags
  236. 17   string     tags        creature tags
  237. 18   %02x                    count of following %08x (unused?)
  238. 19   %08x       trail       repeats for previous count'''
  239.  
  240. def read_header(data):
  241.     '''Returns a tuple of the length of the header,
  242.        and a dict of the information in the header of data.
  243.        Raises ValueError if anything fails to parse.'''
  244.     def pop(n):
  245.         d = ret[0][:n]
  246.         ret[0] = ret[0][n:]
  247.         ret[1] += n
  248.         return d
  249.     def pop_int(name, width):
  250.         ret[name] = int(pop(width), 16)
  251.     def pop_str(name, width, mul=1):
  252.         ret[name] = pop(mul * int(pop(width), 16))
  253.    
  254.     ret = {0: data, 1:0} #this is a hack to allow the functions to modify
  255.                          #external non-global state
  256.     if pop(5) != 'spore':
  257.         raise ValueError
  258.  
  259.     pop_int('version', 4)
  260.     pop_int('tid',     8)
  261.     pop_int('gid',     8)
  262.     pop_int('id',      8)
  263.     pop_int('mid',     8)
  264.     pop_int('cid',    16)
  265.     pop_int('time',   16)
  266.  
  267.     pop_str('uname',   2)
  268.  
  269.     pop_int('uid',    16)
  270.  
  271.     pop_str('name',    2)
  272.     pop_str('desc',    3)
  273.     pop_str('tags',    2)
  274.     pop_str('trail',   2, 8)
  275.  
  276.     del ret[0]
  277.     return ret.pop(1), ret
  278.  
  279. def build_header(head):
  280.     def mask(width):
  281.         return (1<<(8*width)) - 1
  282.     def push_int(name, width, default):
  283.         ret[0] += ("%%0%dx" % width) % (head.get(name, default) & mask(width))
  284.     def push_str(name, width, default, div=1):
  285.         val = head.get(name, default)
  286.         ret[0] += ("%%0%dd" % width) % ((len(val)/div) & mask(width))
  287.         ret[0] += val
  288.  
  289.     ret = {0:'spore'} # the same hack, for external non-global state
  290.  
  291.     push_int('version', 4, 5)
  292.     push_int('tid',     8, 0x2b978c46)
  293.     push_int('gid',     8, 0x40626200)
  294.     push_int('id',      8, 0)
  295.     push_int('mid',     8, 0)
  296.     push_int('cid',    16, 0xffffffffffffffff)
  297.     push_int('time',   16, 63349452846)
  298.  
  299.     push_str('uname',   2, '')
  300.  
  301.     push_int('uid',    16, 0)
  302.  
  303.     push_str('name',    2,'')
  304.     push_str('desc',    3, '')
  305.     push_str('tags',    2, '')
  306.  
  307.     push_str('trail',   2, '', 8)
  308.     return ret[0]
  309.  
  310.  
  311. ###########################################################################
  312. ############ everything past this point is business logic #################
  313. ######### not directly involved in the creature file format ###############
  314. ###########################################################################
  315.  
  316. rep_re = re.compile(r'^[-/\w.]*$')
  317.  
  318. def rep(x):
  319.     if rep_re.match(str(x)):
  320.         return str(x)
  321.     else:
  322.         return repr(x)
  323.  
  324. def fin(f): #file in name
  325.     return rep(f) if f != '-' else 'stdin'
  326.  
  327. def fon(f): #file out name
  328.     return rep(f) if f != '-' else 'stdout'
  329.  
  330.  
  331. def write_output(file, data):
  332.     try:
  333.         file = open(file, "wb") if file != '-' else sys.stdout
  334.         file.write(data)
  335.         if file != sys.stdout:
  336.             file.close()
  337.     except IOError:
  338.         err("unable to write to %s." % fon(file))
  339.  
  340. def decode(file, out, opts):
  341.     alert("Decoding from %s to %s   " %
  342.                 (fin(file), fon(out)))
  343.  
  344.     try:
  345.         creature = decode_creature(file)[1]
  346.         read_header(creature)
  347.     except IOError:
  348.         err("Unable to load image")
  349.     except ValueError:
  350.         alert("warning: malformed creature header")
  351.  
  352.     write_output(out, creature)    
  353.  
  354.     alert("\n")
  355.  
  356. def encode(file, out, opts):
  357.     if opts.image:
  358.         alert("Encoding %s to %s, using image %s   " %
  359.                 (fin(file), fon(out), opts.image))
  360.     else:
  361.         alert("Encoding %s to %s, using a blank image   " %
  362.                 (fin(file), fon(out)))
  363.  
  364.     try:
  365.         data = (open(file, "rb") if file != '-' else sys.stdin).read()
  366.     except IOError:
  367.         err("unable to read data from %s" % fin(file))
  368.    
  369.     if opts.image:
  370.         try:
  371.             im = Image.open(opts.image)
  372.         except IOError:
  373.             err("unable to load image %s" % rep(opts.image))
  374.     else:
  375.         im = None
  376.  
  377.     encode_data(data, im, out)
  378.  
  379.     alert('\n')
  380.  
  381. def identify(file, out, opts):
  382.     if out != '-':
  383.         alert("Dumping metadata of %s to %s" % (fin(file), rep(out)))
  384.  
  385.     try:
  386.         creature = decode_creature(file, ferror=True)[1]
  387.     except IOError:
  388.         # try parsing the file as an image
  389.         try:
  390.             f = open(file, "rb") if file != '-' else sys.stdin
  391.             creature = f.read()
  392.             if f != sys.stdin:
  393.                 f.close()
  394.   #          if any(ord(c) < 20 for c in creature): #detect ASCII
  395.   #              raise IOError
  396.         except IOError:
  397.             err("unable to load creature from %s" % fin(file))
  398.  
  399.     try:
  400.         len, h = read_header(creature)
  401.     except ValueError:
  402.         err("malformed creature header in %s" % fin(file))
  403.  
  404.     try:
  405.         f = open(out, "wb") if out != '-' else sys.stdout
  406.  
  407.         f.write("""Information about %s:
  408.        Creature Name: %-16s (cid:%#13x, id:  %#10x)
  409.           Created by: %-16s (uid:%#13x, mid: %#10x)
  410.                 Date: %s"""
  411. % (fin(file), h['name'], h['cid'] if h['cid'] != (1<<64)-1 else -1, h['id'],
  412.           h['uname'], h['uid'], h['mid'],time.ctime(y0_to_e(h['time']))))
  413.         if h['desc']:
  414.             f.write("""
  415.          Description: %s""" % h['desc'].strip().replace('\n', ' / '))
  416.         if h['tags']:
  417.             f.write("""
  418.                 Tags: %s""" % h['tags'])
  419.         if f != sys.stdout:
  420.             f.close()
  421.         else:
  422.             f.flush()
  423.     except IOError:
  424.         err("unable to write to %s" % fon(out))
  425.  
  426.     alert("\n")
  427.  
  428. def y0_to_e(t):
  429.     #converts seconds since ~year0 to seconds since the epoch
  430.     return t - 62135683200
  431.  
  432. def e_to_y0(t):
  433.     #inverse
  434.     return t + 62135683200
  435.  
  436. def quit(msg, status=1):
  437.     if not opts.quiet:
  438.         alert(msg)
  439.     sys.exit(status)
  440.  
  441. class Error(Exception):
  442.     pass
  443.  
  444. def err(msg):
  445.     alert("error:", msg, "\n")
  446.     raise Error
  447.  
  448. def alert(*msgs):
  449.     if not opts.quiet:
  450.         sys.stderr.write(' '.join(str(msg) for msg in msgs))
  451.         sys.stderr.flush()
  452.  
  453. def debug(*msgs):
  454.     if opts.verbose and not opts.quiet:
  455.         sys.stderr.write(' '.join(str(msg) for msg in msgs))
  456.         sys.stderr.flush()
  457.  
  458. #########################################################################
  459. #                here there be commandline parsing                      #
  460. #########################################################################
  461.  
  462. usage = """Spore Creature Compiler %s""" % version + """
  463.  reverse engineering by Ymgve and Rick, frontend by Scaevolus
  464.  
  465. Usage:  %prog [MODE] [OPTION...] [FILE...]
  466.  
  467. Examples:
  468.  %prog a.png                      # decode a.png to a.png.xml
  469.  %prog *.png                      # decode creatures in all png images
  470.  %prog -m --name Emu Owl.png      # change creature name in Owl.png to "Emu"
  471.  %prog -e -i b.jpg c.xml -o a.png # encode c.xml in image b.jpg, write to a.png
  472.  %prog -l creature.png            # display name, creator, ... of creature.png
  473.  
  474. Modes:
  475.  -x, --decode  extract data from FILE (default output:FILE.xml) (default mode)
  476.  -e, --encode  encode data from FILE (default output:FILE.png, image:white)
  477.  -m, --modify  modify information about creature in FILE. (default output:FILE)
  478.  -l, --list    list information about creature in FILE (default output:STDOUT)
  479. """
  480.  
  481. def parse_timestamp(option, opt, value):
  482.     try:
  483.         if value.lower() == "now":
  484.             t = time.localtime()
  485.         else:
  486.             strip = re.sub(r'[^\d]', '', value)
  487.             format = "%Y%m%d%H%M%S"[:len(strip)-2]
  488.             t = time.strptime(strip, format)
  489.         return epoch_to_year0(int(time.mktime(t)))
  490.     except ValueError:
  491.         raise optparse.OptionValueError(
  492.             "option %s: invalid time format: %r" % (value, value))
  493.  
  494. optparse.Option.TYPES += ("timestamp",) #really, I should subclass and copy
  495. optparse.Option.TYPE_CHECKER["timestamp"] = parse_timestamp #...:effort:
  496.  
  497. class Formatter(optparse.IndentedHelpFormatter):
  498.     def format_usage(self, usage):
  499.         return usage
  500.  
  501. class Parser(optparse.OptionParser, object):
  502.     def error(self, msg): #overload so that usage isn't printed on error
  503.         self.exit(2, "%s: error: %s\n" % (self.get_prog_name(), msg))
  504.  
  505.     def print_help(self, file=sys.stderr):
  506.         super(Parser, self).print_help(file)
  507.  
  508. parser = Parser(usage=usage, version="%prog " + version,
  509.                 formatter=Formatter(max_help_position=30))
  510.  
  511. s = optparse.SUPPRESS_HELP
  512. p = parser.add_option
  513.  
  514. #### MODES
  515. p("-x", "--decode", dest="decode", action="store_true", help=s)
  516. p("-e", "--encode", dest="encode", action="store_true", help=s)
  517. p("-m", "--modify", dest="modify", action="store_true", help=s)
  518. p("-l", "--list",   dest="list",   action="store_true", help=s)
  519.  
  520. ### OPTIONS
  521. p("-q", "--quiet", dest="quiet", action="store_true", help="suppress output")
  522. p("-v", "--verbose", dest="verbose", action="store_true", help="verbose output")
  523. p("-s", "--safe", dest="overwrite", action="store_false", default=True,
  524.         help="don't overwrite existing files")
  525. p('-p', '--preserve', dest='preserve', action="store_true",
  526.     help="don't automatically blank creature id and change instance id " +
  527.          "when encoding or modifying")
  528. p('-o', '--output', dest="outfile", help="file to write output to",
  529.         metavar='FILE')
  530. p('-i', '--image', dest='image', help='image to use (will be scaled to 128x128)'
  531.         +' (-e/--encode or -m/--modify only)', metavar='FILE')
  532.  
  533. ### MODIFIERS
  534. m = optparse.OptionGroup(parser, 'Modifiers', '(-m/--modify only)')
  535. p = m.add_option
  536. p('-n', '--name', dest='name', help='set creature name')
  537. p('-d', '--desc', dest='desc', help='set creature description')
  538. p('-t', '--tags', dest='tags', help='set creature tags')
  539. p('-u', '--user', dest='user', help='set creator name')
  540. p('-c', '--date', dest='time', type='timestamp', help='set creation time '+
  541.  '(format: either "now" or yyyy[mm[dd[HH[MM[SS]]]]], non-digits are ignored)')
  542. p('--cid', dest='cid', help='set creature id (-1 is null)', type="int")
  543. p('--id',  dest= 'id', help='set instance id', type="int")
  544. p('--uid', dest='uid', help='set creator id',  type="int")
  545. p('--mid', dest='mid', help='set machine id',  type="int")
  546. parser.add_option_group(m)
  547.  
  548. opts, args = parser.parse_args()
  549.  
  550. if len(args) == 0:
  551.     if len(sys.argv) > 1:
  552.         parser.error("At least one file must be specified")
  553.     parser.print_help()
  554.     exit(1)
  555.  
  556. if sum(1 for x in (opts.decode, opts.encode, opts.modify, opts.list) if x) > 1:
  557.     parser.error("more than one mode specified")
  558. mode = ("encode" if opts.encode else "modify" if opts.modify else
  559.         "list" if opts.list else "decode") #decode is the default
  560.  
  561. if mode != "modify" and [opts.name, opts.desc, opts.tags, opts.user, opts.time,
  562.                 opts.cid, opts.id, opts.uid, opts.mid].count(None) != 9:
  563.     parser.error("modifiers are only valid in modify mode (-m/--modify)")
  564.  
  565. if mode != "encode" and opts.image:
  566.     parser.error("image is only valid in encode mode (-e/--encode)")
  567.  
  568. del parse_timestamp, Formatter, Parser, parser, s, p #cleanup
  569.  
  570. mode_func = {'decode': decode, 'list': identify, 'encode': encode}[mode]
  571. mode_formats = {'decode': '%(s)s.xml', 'list': '-', 'encode': '%(s)s.png'}
  572.  
  573. def get_outfile(infile, outfile):
  574.     if not outfile:
  575.         o = mode_formats[mode] % {'s':infile}
  576.     else:
  577.         o = outfile
  578.     if o != '-' and os.path.exists(o) and not opts.overwrite:
  579.         err("%s exists, not overwriting" % rep(out))
  580.     return o
  581.  
  582. argn = len(args)
  583. argw = len(str(argn))
  584.  
  585. for num, file in enumerate(args):
  586.     sys.stdout.flush()
  587.     sys.stderr.flush()
  588.     if argn > 1:
  589.         alert(("[%%%dd/%d] " % (argw, argn)) % (num + 1))
  590.     try:
  591.         mode_func(file, get_outfile(file, opts.outfile), opts)
  592.     except Error:
  593.         pass
  594.     except KeyboardInterrupt:
  595.         alert("\ninterrupt!\n")
  596.         sys.exit(3)
  597.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement