packpanda.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #############################################################################
  2. #
  3. # packpanda - this is a tool that packages up a panda game into a
  4. # convenient, easily-downloaded windows executable. Packpanda relies on
  5. # NSIS, the netscape scriptable install system, to do the hard work.
  6. #
  7. # This is intentionally a very simplistic game-packer with very
  8. # limited options. The goal is simplicity, not feature richness.
  9. # There are dozens of complex, powerful packaging tools already out
  10. # there. This one is for people who just want to do it quick and
  11. # easy.
  12. #
  13. ##############################################################################
  14. import sys, os, getopt, string, shutil, py_compile
  15. OPTIONLIST = [
  16. ("dir", 1, "Name of directory containing game"),
  17. ("name", 1, "Human-readable name of the game"),
  18. ("version", 1, "Version number to add to game name"),
  19. ("rmdir", 2, "Delete all directories with given name"),
  20. ("rmext", 2, "Delete all files with given extension"),
  21. ("fast", 0, "Use fast compression instead of good compression"),
  22. ("bam", 0, "Generate BAM files, change default-model-extension to BAM"),
  23. ("pyc", 0, "Generate PYC files"),
  24. ]
  25. def ParseFailure():
  26. print ""
  27. print "packpanda usage:"
  28. print ""
  29. for (opt, hasval, explanation) in OPTIONLIST:
  30. if (hasval):
  31. print " --%-10s %s"%(opt+" x", explanation)
  32. else:
  33. print " --%-10s %s"%(opt+" ", explanation)
  34. sys.exit(1)
  35. def ParseOptions(args):
  36. try:
  37. options = {}
  38. longopts = []
  39. for (opt, hasval, explanation) in OPTIONLIST:
  40. if (hasval==2):
  41. longopts.append(opt+"=")
  42. options[opt] = []
  43. elif (hasval==1):
  44. longopts.append(opt+"=")
  45. options[opt] = ""
  46. else:
  47. longopts.append(opt)
  48. options[opt] = 0
  49. opts, extras = getopt.getopt(args, "", longopts)
  50. for option, value in opts:
  51. for (opt, hasval, explanation) in OPTIONLIST:
  52. if (option == "--"+opt):
  53. if (hasval==2): options[opt].append(value)
  54. elif (hasval==1): options[opt] = value
  55. else: options[opt] = 1
  56. return options
  57. except: ParseFailure();
  58. OPTIONS = ParseOptions(sys.argv[1:])
  59. ##############################################################################
  60. #
  61. # Locate the relevant trees.
  62. #
  63. ##############################################################################
  64. PANDA=None
  65. for dir in sys.path:
  66. if (dir != "") and os.path.exists(os.path.join(dir,"direct")) and os.path.exists(os.path.join(dir,"pandac")):
  67. PANDA=os.path.abspath(dir)
  68. if (PANDA is None):
  69. sys.exit("Cannot locate the panda root directory in the python path (cannot locate directory containing direct and pandac).")
  70. print "PANDA located at "+PANDA
  71. if (os.path.exists(os.path.join(PANDA,"..","makepanda","makepanda.py"))) and (os.path.exists(os.path.join(PANDA,"..","thirdparty","win-nsis","makensis.exe"))):
  72. PSOURCE=os.path.abspath(os.path.join(PANDA,".."))
  73. NSIS=os.path.abspath(os.path.join(PANDA,"..","thirdparty","win-nsis"))
  74. else:
  75. PSOURCE=PANDA
  76. NSIS=os.path.join(PANDA,"nsis")
  77. ##############################################################################
  78. #
  79. # Identify the main parts of the game: DIR, NAME, MAIN, ICON, BITMAP, etc
  80. #
  81. ##############################################################################
  82. VER=OPTIONS["version"]
  83. DIR=OPTIONS["dir"]
  84. if (DIR==""):
  85. print "You must specify the --dir option."
  86. ParseFailure()
  87. DIR=os.path.abspath(DIR)
  88. NAME=os.path.basename(DIR)
  89. if (OPTIONS["name"] != ""):
  90. NAME=OPTIONS["name"]
  91. SMDIRECTORY=NAME
  92. if (VER!=""): SMDIRECTORY=SMDIRECTORY+" "+VER
  93. ICON=os.path.join(DIR, "icon.ico")
  94. BITMAP=os.path.join(DIR, "installer.bmp")
  95. LICENSE=os.path.join(DIR, "license.txt")
  96. OUTFILE=os.path.basename(DIR)
  97. if (VER!=""): OUTFILE=OUTFILE+"-"+VER
  98. OUTFILE=os.path.abspath(OUTFILE+".exe")
  99. INSTALLDIR='C:\\'+os.path.basename(DIR)
  100. if (VER!=""): INSTALLDIR=INSTALLDIR+"-"+VER
  101. COMPRESS="lzma"
  102. if (OPTIONS["fast"]): COMPRESS="zlib"
  103. if (OPTIONS["pyc"]): MAIN="main.pyc"
  104. else: MAIN="main.py"
  105. def PrintFileStatus(label, file):
  106. if (os.path.exists(file)):
  107. print "%-15s: %s"%(label, file)
  108. else:
  109. print "%-15s: %s (MISSING)"%(label, file)
  110. PrintFileStatus("Dir", DIR)
  111. print "%-15s: %s"%("Name", NAME)
  112. print "%-15s: %s"%("Start Menu", SMDIRECTORY)
  113. PrintFileStatus("Main", os.path.join(DIR, MAIN))
  114. PrintFileStatus("Icon", ICON)
  115. PrintFileStatus("Bitmap", BITMAP)
  116. PrintFileStatus("License", LICENSE)
  117. print "%-15s: %s"%("Output", OUTFILE)
  118. print "%-15s: %s"%("Install Dir", INSTALLDIR)
  119. if (os.path.isdir(DIR)==0):
  120. sys.exit("Difficulty reading "+DIR+". Cannot continue.")
  121. if (os.path.isfile(os.path.join(DIR, "main.py"))==0):
  122. sys.exit("Difficulty reading main.py. Cannot continue.")
  123. if (os.path.isfile(LICENSE)==0):
  124. LICENSE=os.path.join(PANDA,"LICENSE")
  125. if (os.path.isfile(BITMAP)==0):
  126. BITMAP=os.path.join(NSIS,"Contrib","Graphics","Wizard","nsis.bmp")
  127. if (os.path.isfile(ICON)==0):
  128. PPICON="bin\\ppython.exe"
  129. else:
  130. PPICON="game\\icon.ico"
  131. ##############################################################################
  132. #
  133. # Copy the game to a temporary directory, so we can modify it safely.
  134. #
  135. ##############################################################################
  136. def limitedCopyTree(src, dst, rmdir):
  137. if (os.path.isdir(src)):
  138. if (rmdir.has_key(os.path.basename(src))):
  139. return
  140. os.mkdir(dst)
  141. for x in os.listdir(src):
  142. limitedCopyTree(os.path.join(src,x), os.path.join(dst,x), rmdir)
  143. else:
  144. shutil.copyfile(src, dst)
  145. TMPDIR=os.path.abspath("packpanda-TMP")
  146. TMPGAME=os.path.join(TMPDIR,"game")
  147. TMPETC=os.path.join(TMPDIR,"etc")
  148. print ""
  149. print "Copying the game to "+TMPDIR+"..."
  150. if (os.path.exists(TMPDIR)):
  151. try: shutil.rmtree(TMPDIR)
  152. except: sys.exit("Cannot delete "+TMPDIR)
  153. try:
  154. os.mkdir(TMPDIR)
  155. rmdir = {}
  156. for x in OPTIONS["rmdir"]:
  157. rmdir[x] = 1
  158. limitedCopyTree(DIR, TMPGAME, rmdir)
  159. if not os.path.isdir( TMPGAME ):
  160. os.mkdir(TMPGAME)
  161. limitedCopyTree(os.path.join(PANDA, "etc"), TMPETC, {})
  162. if not os.path.isdir( TMPETC ):
  163. os.mkdir(TMPETC)
  164. except: sys.exit("Cannot copy game to "+TMPDIR)
  165. ##############################################################################
  166. #
  167. # If --bam requested, change default-model-extension .egg to bam.
  168. #
  169. ##############################################################################
  170. def ReadFile(wfile):
  171. try:
  172. srchandle = open(wfile, "rb")
  173. data = srchandle.read()
  174. srchandle.close()
  175. return data
  176. except: exit("Cannot read "+wfile)
  177. def WriteFile(wfile,data):
  178. try:
  179. dsthandle = open(wfile, "wb")
  180. dsthandle.write(data)
  181. dsthandle.close()
  182. except: exit("Cannot write "+wfile)
  183. if OPTIONS["bam"]:
  184. CONF=ReadFile(os.path.join(TMPETC,"Confauto.prc"))
  185. CONF=CONF.replace("default-model-extension .egg","default-model-extension .bam")
  186. WriteFile(os.path.join(TMPETC,"Confauto.prc"), CONF)
  187. ##############################################################################
  188. #
  189. # Compile all py files, convert all egg files.
  190. #
  191. # We do this as a sanity check, even if the user
  192. # hasn't requested that his files be compiled.
  193. #
  194. ##############################################################################
  195. EGG2BAM=os.path.join(PANDA,"bin","egg2bam.exe")
  196. def egg2bam(file):
  197. bam = file[:-4]+'.bam'
  198. present = os.path.exists(bam)
  199. if (present): bam = "packpanda-TMP.bam";
  200. cmd = 'egg2bam -noabs -ps rel -pd . "'+file+'" -o "'+bam+'"'
  201. print "Executing: "+cmd
  202. res = os.spawnl(os.P_WAIT, EGG2BAM, cmd)
  203. if (res != 0): sys.exit("Problem in egg file: "+file)
  204. if (present) or (OPTIONS["bam"]==0):
  205. os.unlink(bam)
  206. def py2pyc(file):
  207. print "Compiling python "+file
  208. pyc = file[:-3]+'.pyc'
  209. pyo = file[:-3]+'.pyo'
  210. if (os.path.exists(pyc)): os.unlink(pyc)
  211. if (os.path.exists(pyo)): os.unlink(pyo)
  212. try: py_compile.compile(file)
  213. except: sys.exit("Cannot compile "+file)
  214. if (OPTIONS["pyc"]==0):
  215. if (os.path.exists(pyc)):
  216. os.unlink(pyc)
  217. if (os.path.exists(pyo)):
  218. os.unlink(pyo)
  219. def CompileFiles(file):
  220. if (os.path.isfile(file)):
  221. if (string.lower(file[-4:])==".egg"):
  222. egg2bam(file)
  223. elif (string.lower(file[-3:])==".py"):
  224. py2pyc(file)
  225. else: pass
  226. elif (os.path.isdir(file)):
  227. for x in os.listdir(file):
  228. CompileFiles(os.path.join(file, x))
  229. def DeleteFiles(file):
  230. base = string.lower(os.path.basename(file))
  231. if (os.path.isdir(file)):
  232. for pattern in OPTIONS["rmdir"]:
  233. if (string.lower(pattern) == base):
  234. print "Deleting "+file
  235. shutil.rmtree(file)
  236. return
  237. for x in os.listdir(file):
  238. DeleteFiles(os.path.join(file, x))
  239. else:
  240. for ext in OPTIONS["rmext"]:
  241. if (base[-(len(ext)+1):] == string.lower("."+ext)):
  242. print "Deleting "+file
  243. os.unlink(file)
  244. return
  245. print ""
  246. print "Compiling BAM and PYC files..."
  247. os.chdir(TMPGAME)
  248. CompileFiles(".")
  249. DeleteFiles(".")
  250. ##############################################################################
  251. #
  252. # Run NSIS. Yay!
  253. #
  254. ##############################################################################
  255. CMD=NSIS+"\\makensis.exe /V2 "
  256. CMD=CMD+'/DCOMPRESSOR="'+COMPRESS+'" '
  257. CMD=CMD+'/DNAME="'+NAME+'" '
  258. CMD=CMD+'/DSMDIRECTORY="'+SMDIRECTORY+'" '
  259. CMD=CMD+'/DINSTALLDIR="'+INSTALLDIR+'" '
  260. CMD=CMD+'/DOUTFILE="'+OUTFILE+'" '
  261. CMD=CMD+'/DLICENSE="'+LICENSE+'" '
  262. CMD=CMD+'/DLANGUAGE="English" '
  263. CMD=CMD+'/DRUNTEXT="Play '+NAME+'" '
  264. CMD=CMD+'/DIBITMAP="'+BITMAP+'" '
  265. CMD=CMD+'/DUBITMAP="'+BITMAP+'" '
  266. CMD=CMD+'/DPANDA="'+PANDA+'" '
  267. CMD=CMD+'/DPANDACONF="'+TMPETC+'" '
  268. CMD=CMD+'/DPSOURCE="'+PSOURCE+'" '
  269. CMD=CMD+'/DPPGAME="'+TMPGAME+'" '
  270. CMD=CMD+'/DPPMAIN="'+MAIN+'" '
  271. CMD=CMD+'/DPPICON="'+PPICON+'" '
  272. CMD=CMD+'"'+PSOURCE+'\\direct\\src\\directscripts\\packpanda.nsi"'
  273. print ""
  274. print CMD
  275. print "packing..."
  276. os.system(CMD)