SHARE
TWEET

c3mm.py

7163D Oct 7th, 2016 75 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Cossacks 3 Mod Manager version 1.3.10
  2. # Require python 3.5.x to work : https://python.org/downloads
  3. # Get all update at http://www.cossacks3.com/forum/index.php?threads/cossacks-3-text-mod-manager.17753
  4.  
  5. """
  6. # === News in 1.3.10 === #
  7.     >New commands installs and uninstalls
  8.     >New command install_all, which install all mods of mod folder
  9.     >c3mm.json is most easy to read
  10.     >Fields "remove_file", "add_file", "modify_file", "replace_file", are now optional
  11.     >Fix some bugs
  12.  
  13. """
  14.  
  15. import json, sys, os, time
  16. from random import randrange
  17. from typing import List, Dict
  18.  
  19.  
  20. Mod = {"name":str, "version":str, "info":str, "author":str, "file":{str:{str:str}}, "otherfile":{str:str}, "addfile":{str:str}, "removefile":[str]}
  21. Data = {"mod":{str:Mod}, "file":{str:[Mod]}, "otherfile":{str:[Mod]}, "remove_file":{str:[Mod]},"addfile":{str:[Mod]}}
  22.  
  23. CONFIG_PATH = "c3mm/"
  24. MOD_PATH    = "mod/"
  25. COPY_PATH   = "c3mm/copy/"
  26. BCKUP_PATH  = "c3mm/bckup/"
  27. INFO_PATH   = "c3mm/c3mm.json"
  28. LOG_PATH    = "c3mm/log.txt"
  29. HELP = """
  30. Cossacks3 (text) Mod Manager Version 1.3.10
  31. Type python c3mm.py action [parameter]
  32.     action:
  33.         install modname          -> intall a mod find by name
  34.         uninstall modname        -> unintall a mod find by name
  35.         installs mod1 mod2 ...   -> install mod1, mod2 ...
  36.         uninstalls mod1 mod2 ... -> uninstall mod1, mod2 ...
  37.         help                     -> show this message
  38.         install_all              -> install all mods of mod folder
  39.         uninstall_all            -> uninstall all mod
  40.         giveall                  -> print all installed mods
  41.         valid_mod modname        -> check if mod modname is valid
  42.         info modname             -> print all informations about mod modname
  43. """
  44.  
  45. PATH = [CONFIG_PATH, MOD_PATH, COPY_PATH, BCKUP_PATH]
  46. FILE = [INFO_PATH, LOG_PATH]
  47. for path in PATH:
  48.     try:
  49.         os.mkdir(path)
  50.     except:
  51.         pass
  52. for file in FILE:
  53.     try:
  54.         open(file, 'r')
  55.     except:
  56.         open(file, 'w')
  57.  
  58. class Alert:
  59.     linfo = []
  60.     lerror = []
  61.     show = True
  62.  
  63.     def info(i):
  64.         if Alert.show:
  65.             Alert.linfo.append(i)
  66.         print(i)
  67.  
  68.     def error(e, data):
  69.         Alert.lerror.append(e)
  70.         if Alert.show:
  71.             print(e)
  72.         quit(data)
  73.  
  74.     def input(m):
  75.         return input(m)
  76.  
  77.     def list(l):
  78.         for i in l:
  79.             if Alert.show:
  80.                 print(i)
  81.  
  82. def loadjson(path: str) -> str:
  83.     return json.loads(open(path, 'r').read())
  84.  
  85. def savejson(path: str, data: Data) -> None:
  86.     open(path, "w").write(nicejson(json.dumps(data)))
  87.  
  88. def loadjsonmod(path: str) -> Mod:
  89.    
  90.     try:
  91.         mod = loadjson(path)
  92.     except:
  93.         Alert.info("Impossible to open " + path)
  94.         return False
  95.  
  96.     for field in ["add_file", "modify_file", "replace_file"]:
  97.         if not field in mod:
  98.             mod[field] = {}
  99.     for field in ["remove_file"]:
  100.         if not field in mod:
  101.             mod[field] = []
  102.  
  103.     if not "file" in mod:
  104.         mod["file"] = mod["modify_file"]
  105.     if not "otherfile" in mod:
  106.         mod["otherfile"] = mod["replace_file"]
  107.     if not "addfile" in mod:
  108.         mod["addfile"] = mod["add_file"]
  109.  
  110.  
  111.     if not validMod(mod):
  112.         return False
  113.  
  114.     return mod
  115.  
  116. def nicejson(txt):
  117.     ntxt = ""
  118.     n = 0
  119.     IN = 0
  120.     for l in txt:
  121.         if l == '"':
  122.             IN = 1 - IN
  123.         if IN:
  124.             ntxt += l
  125.         elif l == ",":
  126.             ntxt += ",\n" + n * "\t"
  127.         elif l == "{" or l == "[":
  128.             ntxt += l + "\n" + (n+1) * "\t"
  129.             n += 1
  130.         elif l == "}" or l == "]":
  131.             n -= 1
  132.             ntxt += "\n" + n * "\t" + l
  133.         else:
  134.             ntxt += l
  135.     return ntxt
  136.  
  137. def mkPath(path: str) -> None:
  138.     rpath = ""
  139.     for dir in path.split('/')[:-1]:
  140.         rpath += dir
  141.         try:
  142.             os.mkdir(rpath)
  143.         except:
  144.             pass
  145.         rpath += '/'
  146.  
  147. def copyBFile(file: str, path: str) -> None:
  148.     mkPath(path)
  149.     open(path, "wb").write(open(file, 'rb').read())
  150.  
  151. def checkMod(mod: Mod, data: Data) -> bool:
  152.     """
  153.     checkMod function, use to check if a mod is valid
  154.     Mod mod     -> mod to check
  155.     Bool return -> is the mod valid?
  156.     """
  157.     for file in mod['file']:
  158.         Alert.info("Check existence of '" + file + "'")
  159.         try:
  160.             f = open(file, 'r').read()
  161.             Alert.info("'" + file + "' exist.")
  162.         except:
  163.             Alert.info("'" + file + "' don't exist.")
  164.             return False
  165.  
  166.         for old in mod['file'][file]:
  167.             Alert.info("Check exsitence of '" + old + "' in '" + file + "'")
  168.             if not old in f:
  169.                 Alert.info("Not found.")
  170.                 return False
  171.             else:
  172.                 Alert.info("Found.")
  173.  
  174.     for file in mod["otherfile"]:
  175.         Alert.info("Check existence of '" + file + "'")
  176.         try:
  177.             open(file, 'r')
  178.             Alert.info("'" + file + "' exist.")
  179.         except:
  180.             Alert.info("'" + file + "' don't exist.")
  181.             return False
  182.  
  183.         Alert.info("Check existence of '" + MOD_PATH + mod["otherfile"][file] + "'")
  184.         try:
  185.             open(MOD_PATH + mod["otherfile"][file], 'r')
  186.             Alert.info("'" + MOD_PATH + mod["otherfile"][file] + "' exist.")
  187.         except:
  188.             Alert.info("'" + MOD_PATH + mod["otherfile"][file] + "' don't exist.")
  189.             return False
  190.  
  191.         if file in data["otherfile"]:
  192.             Alert.info(file + " is already modified by " + data["otherfile"][file][0])
  193.             Alert.info("You have to uninstall " + data["otherfile"][file][0] + " to install " + mod['name'])
  194.             return False
  195.  
  196.     for file in mod["addfile"]:
  197.         Alert.info("Check existence of '" + MOD_PATH + mod["addfile"][file] + "'")
  198.         try:
  199.             open(MOD_PATH + mod["addfile"][file], 'r')
  200.             Alert.info("'" + MOD_PATH + mod["addfile"][file] + "' exist.")
  201.         except:
  202.             Alert.info("'" + MOD_PATH + mod["addfile"][file] + "' don't exist.")
  203.             return False
  204.  
  205.         if file in data["addfile"]:
  206.             Alert.info(file + " is already added by " + data["addfile"][file][0])
  207.             Alert.info("You have to uninstall " + data["addfile"][file][0] + " to install " + mod['name'])
  208.             return False
  209.  
  210.     return True
  211.  
  212. def validMod(mod: Mod) -> bool:
  213.     """
  214.     validMod function, assert not error occured when install mod
  215.     Mod mod -> mod to check
  216.     Bool return
  217.     """
  218.     for field in ['name', 'info', 'file', 'version', 'author']:
  219.         if not field in mod:
  220.             Alert.info("Missing field '" + field + "'")
  221.             return False
  222.  
  223.     if not type(mod['file']) == type({}):
  224.         Alert.info("Wrong data mod")
  225.         return False
  226.  
  227.     for file in mod['file']:
  228.         if not type(mod['file'][file]) == type({}):
  229.             Alert.info("Wrong data mod")
  230.             return False
  231.         try:
  232.             for old in mod['file'][file]:
  233.                 new = mod['file'][file][old]
  234.                 if not type(old) == type("") or not type(new) == type(""):
  235.                     Alert.info("Wrong data mod")
  236.                     return False
  237.         except:
  238.             Alert.info("Wrong data mod")
  239.             return False
  240.  
  241.     if not type(mod['otherfile']) == type({}):
  242.         Alert.info("Wrong data mod, replace_file must be a dict, not a " + str(type(mod['otherfile'])))
  243.         return False
  244.  
  245.     if not type(mod['addfile']) == type({}):
  246.         Alert.info("Wrong data mod, add_file must be a dict, not a " + str(type(mod['addfile'])))
  247.         return False
  248.  
  249.     if not type(mod['remove_file']) == type([]):
  250.         Alert.info("Wrong data mod, remove_file must be a list, not a " + str(type(mod['remove_file'])))
  251.         return False
  252.  
  253.     return True
  254.  
  255.  
  256. def apply(mod: Mod) -> None:
  257.     """
  258.     apply function, use to modify files
  259.     Mod mod -> mod that contain all modifications
  260.     None return
  261.     """
  262.     mfile = {}
  263.     for file in mod['file']:
  264.         Alert.info("Calculate'" + file + '"')
  265.         f = open(file, 'r').read()
  266.  
  267.         #Calculate modifications
  268.         for old in mod['file'][file]:
  269.             new = mod['file'][file][old]
  270.             f = f.replace(old, new)
  271.  
  272.         mfile[file] = f
  273.    
  274.     #Apply mod
  275.     for file in mfile:
  276.         Alert.info("Patching '" + file + '"')
  277.         open(file, 'w').write(mfile[file])
  278.  
  279.     for file in mod["otherfile"]:
  280.         Alert.info("Replace '" + file+ "'")
  281.         copyBFile(MOD_PATH + mod["otherfile"][file], file)
  282.  
  283.     for file in mod["addfile"]:
  284.         Alert.info("Add '" + file + "'")
  285.         copyBFile(MOD_PATH + mod["addfile"][file], file)
  286.  
  287.     for file in mod["remove_file"]:
  288.         Alert.info("Remove'" + file + "'")
  289.         os.remove(file)
  290.  
  291. def install(mod: Mod, data: Data) -> None:
  292.     """
  293.     install function, use to add a mod.
  294.     Mod mod   -> mod to install
  295.     Data data -> c3mm main data
  296.     None return
  297.     """
  298.     modname = mod['name']
  299.  
  300.     if modname in data['mod']:
  301.  
  302.         if mod["version"] == data["mod"][modname]["version"]:
  303.             Alert.info("Mod '" + modname + "' version " + mod["version"] + " is already install.")
  304.             return
  305.         else:
  306.             Alert.info("Mod '" + modname + "' version " + data["mod"][modname]["version"] + " is already install. Do you want change it for version " + mod["version"] + "?")
  307.             choice = Alert.input("yes/no:")
  308.             if choice == "no":
  309.                 quit() 
  310.  
  311.             uninstall(data['mod'][modname], data)
  312.  
  313.     if not checkMod(mod, data):
  314.         Alert.error("'" + mod['name'] + "' mod version " + mod['version'] + " isn't valid", data)
  315.  
  316.     Alert.info("Install '" + mod['name'] + "' version " + mod['version'])
  317.  
  318.     #Add some "signature"
  319.     for file in mod['file']:
  320.         if not ".script" in file:
  321.             continue
  322.         for old in mod['file'][file]:
  323.             mod['file'][file][old] = mod['file'][file][old] + "//" + str(randrange(100000, 999999))
  324.  
  325.     #Add mod to list of installed mod
  326.     data['mod'][mod['name']] = mod
  327.  
  328.     #Adding mod in list of file modification
  329.     for file in mod['file']:
  330.         if not file in data['file']:
  331.             data['file'][file] = [mod['name']]
  332.             #backup file
  333.             copyBFile(file, BCKUP_PATH + file)
  334.         else:
  335.             data['file'][file].append(mod['name'])
  336.  
  337.     for file in mod["otherfile"]:
  338.         if not file in data['otherfile']:
  339.             data['otherfile'][file] = [mod['name']]
  340.             #backup file
  341.             copyBFile(file, BCKUP_PATH + file)
  342.         else:
  343.             data['otherfile'][file].append(mod['name'])
  344.  
  345.     for file in mod["addfile"]:
  346.         if not file in data['otherfile']:
  347.             data['addfile'][file] = [mod['name']]
  348.         else:
  349.             data['addfile'][file].append(mod['name'])
  350.  
  351.     for file in mod["remove_file"]:
  352.         copyBFile(file, BCKUP_PATH + file)
  353.         data["remove_file"][file] = [mod['name']]
  354.  
  355.     apply(mod)
  356.  
  357.     for file in mod['file']:
  358.         #make a save of the file
  359.         copyBFile(file, COPY_PATH + file)
  360.  
  361.     for file in mod['otherfile']:
  362.         #make a save of the file
  363.         copyBFile(file, COPY_PATH + file)
  364.  
  365. def uninstall(mod: Mod, data: Data) -> None:
  366.     """
  367.     uninstall function, use to delete a mod.
  368.     Mod mod   -> mod to uninstall
  369.     Data data -> c3mm main data
  370.     None return
  371.     """
  372.     uninstallmod = mkuninstallmod(mod)
  373.  
  374.     if not checkMod(uninstallmod, data):
  375.         Alert.info("'" + mod['name'] + "' mod uninstaller isn't valid.")
  376.         Alert.info("Do you want restore all files concerned by this mod?")
  377.         Alert.info("List of mod will uninstalled in this case:")
  378.         modlist = []
  379.         for file in mod['file']:
  380.             for mod in data['file'][file]:
  381.                 if not mod in modlist:
  382.                     modlist.append(mod)
  383.                     Alert.info(mod)
  384.         choice = Alert.input("Restore all file? yes/no:")
  385.         if choice == 'no':
  386.             quit(data)
  387.  
  388.         filelist = [file for file in mod['file']]
  389.         for file in filelist:
  390.             Alert("Restore '" + file + "'")
  391.             copyBFile(BCKUP_PATH + file, file)
  392.             for mod in modlist:
  393.                 if file in mod["file"]:
  394.                     del mod["file"][file]
  395.                 if file in mod["otherfile"]:
  396.                     del mod["otherfile"][file]
  397.                 if file in mod["addfile"]:
  398.                     del mod["addfile"][file]
  399.                 if file in mod["remove_file"]:
  400.                     mod["remove_file"].remove("file")
  401.  
  402.         for mod in modlist:
  403.             uninstall(mod, data)
  404.  
  405.     #Delete mod to list of installed mod
  406.     del data['mod'][mod['name']]
  407.  
  408.     #Delete mod of list of file modification
  409.     for file in data['file']:
  410.         if mod['name'] in data['file'][file]:
  411.             data['file'][file].remove(mod['name'])
  412.  
  413.     Alert.info("Uninstall '" + mod['name'] + "' version " + mod['version'])
  414.  
  415.     for file in mod["otherfile"]:
  416.         Alert.info("Restore " + file)
  417.         copyBFile(BCKUP_PATH + file, file)
  418.         del data["otherfile"][file]
  419.  
  420.     for file in mod["addfile"]:
  421.         Alert.info("Delete " + file)
  422.         os.remove(file)
  423.         del data["addfile"][file]
  424.  
  425.     for file in mod["remove_file"]:
  426.         Alert.info("Restore " + file)
  427.         copyBFile(BCKUP_PATH + file, file)
  428.         del data["remove_file"][file]
  429.  
  430.     apply(uninstallmod)
  431.  
  432. def mkuninstallmod(mod: Mod) -> None:
  433.     """
  434.     mkuninstallmod function, use to make an uninstalator from a mod
  435.     Mod mod    -> mod to uninstall
  436.     Mod return -> uninstalator mod
  437.     """
  438.     uninstallmod = {}
  439.     uninstallmod['file'] = {}
  440.  
  441.     for file in mod['file']:
  442.         uninstallmod['file'][file] = {}
  443.         for old in mod['file'][file]:
  444.             new = mod['file'][file][old]
  445.             #For each modification of mod, inverse new and old
  446.             uninstallmod['file'][file][new] = old
  447.  
  448.     uninstallmod["otherfile"] = {}
  449.     uninstallmod["addfile"] = {}
  450.     uninstallmod["remove_file"] = {}
  451.  
  452.     return uninstallmod
  453.  
  454. def checkStatu(data:  Data) -> bool:
  455.     """
  456.     checkStatu funtion, check if a modded file has been modified since last launch of this program
  457.     Data data   -> main data
  458.     Bool return ->
  459.     """
  460.     badfile = []
  461.     #Check all modded file
  462.     for file in data['file']:
  463.         if open(file, 'rb').read() != open(COPY_PATH + file, 'rb').read():
  464.             Alert.info("File '" + file + "' has been modified since last launch of c3mm.")
  465.             badfile.append(file)
  466.  
  467.     badotherfile = []
  468.     for file in data["otherfile"]:
  469.         if open(file, 'rb').read() != open(COPY_PATH + file, 'rb').read():
  470.             Alert.info("File '" + file + "' has been modified since last launch of c3mm.")
  471.             badotherfile.append(file)
  472.  
  473.     badmod = []
  474.     #Uninstall all mod concerned by these files
  475.     for file in badfile:
  476.         for mod in data['file'][file]:
  477.             if not mod in badmod and not checkMod(mkuninstallmod(data['mod'][mod]), data):
  478.                 badmod.append(mod)
  479.     for file in badotherfile:
  480.         if not data["otherfile"][file][0] in badmod:
  481.             badmod.append(data["otherfile"][file][0])
  482.  
  483.     for mod in badmod:
  484.         _mod = data['mod'][mod]
  485.         for file in badfile:
  486.             if file in _mod['file']:
  487.                 del _mod['file'][file]
  488.         uninstall(_mod, data)
  489.  
  490.     #make a new copy of all modified files
  491.     for file in badfile:
  492.         copyBFile(file, COPY_PATH + file)
  493.  
  494.     return len(badmod) == 0
  495.  
  496. def quit(data: Data) -> None:
  497.     """
  498.     Save all data before exit the program
  499.     """
  500.     dfile = []
  501.     for file in data['file']:
  502.         if data['file'][file] == []:
  503.             dfile.append(file)
  504.  
  505.     for file in dfile:
  506.         del data['file'][file]
  507.  
  508.     savejson(INFO_PATH, data)
  509.  
  510.     logs = time.strftime('%D %H:%M\n',time.localtime())
  511.     for info in Alert.linfo:
  512.         logs += info + '\n'
  513.     for error in Alert.lerror:
  514.         logs += error + '\n'
  515.  
  516.     open(LOG_PATH, "a").write(logs)
  517.     exit()
  518.  
  519. def main(argc: int, argv: List[str]) -> None:
  520.  
  521.     try:
  522.         data = loadjson(INFO_PATH)
  523.     except:
  524.         open(INFO_PATH, "w").write('{"mod":{}, "file":{}}')
  525.         data = loadjson(INFO_PATH)
  526.  
  527.     if not "otherfile" in data:
  528.         data["otherfile"] = {}
  529.  
  530.     if not "addfile" in data:
  531.         data["addfile"] = {}
  532.  
  533.     if not "remove_file" in data:
  534.         data["remove_file"] = {}
  535.  
  536.     if not checkStatu(data):
  537.         quit(data)
  538.  
  539.     elif argc == 1 or argv[1] == "help":
  540.         print(HELP)
  541.  
  542.     elif argv[1] == 'install':
  543.  
  544.         if argc < 3:
  545.             Alert.error("You must give a mod name", data)
  546.  
  547.         mod = loadjsonmod(MOD_PATH + argv[2] + '.json')
  548.         if not mod:
  549.             Alert.error("Can't load mod.", data)
  550.         install(mod, data)
  551.  
  552.     elif argv[1] == "installs":
  553.  
  554.         if argc < 3:
  555.             Alert.error("You must give a mod name", data)
  556.  
  557.         for path in argv[2:]:
  558.             mod = loadjsonmod(MOD_PATH + path + '.json')
  559.             if not mod:
  560.                 Alert.error("Can't load mod.", data)
  561.             install(mod, data)
  562.        
  563.     elif argv[1] == "install_all":
  564.         for path in [file.name for file in os.scandir(MOD_PATH) if ".json" in file.name]:
  565.             mod = loadjsonmod(MOD_PATH + path)
  566.             if not mod:
  567.                 Alert.error("Can't load mod.", data)
  568.             install(mod, data)
  569.  
  570.     elif argv[1] == 'uninstall':
  571.  
  572.         if argc < 3:
  573.             Alert.error("You must give a mod name", data)
  574.  
  575.         mod = loadjsonmod(MOD_PATH + argv[2] + '.json')
  576.         modname = mod['name']
  577.  
  578.         if not modname in data['mod']:
  579.             Alert.error(mod + " isn't installed!")
  580.  
  581.         uninstall(mod, data)
  582.  
  583.     elif argv[1] == 'uninstalls':
  584.  
  585.         if argc < 3:
  586.             Alert.error("You must give a mod name", data)
  587.  
  588.         for path in argv[2:]:
  589.             mod = loadjsonmod(MOD_PATH + argv[2] + '.json')
  590.             modname = mod['name']
  591.  
  592.             if not modname in data['mod']:
  593.                 Alert.error(mod + " isn't installed!")
  594.  
  595.             uninstall(mod, data)
  596.  
  597.     elif argv[1] == 'uninstall_all':
  598.  
  599.         Alert.info("Restore all files")
  600.         for file in data['file']:
  601.             Alert.info("Restore '" + file + "'")
  602.             copyBFile(BCKUP_PATH + file, file)
  603.         for file in data['otherfile']:
  604.             Alert.info("Restore '" + file + "'")
  605.             copyBFile(BCKUP_PATH + file, file)
  606.         for file in data['remove_file']:
  607.             Alert.info("Restore '" + file + "'")
  608.             copyBFile(BCKUP_PATH + file, file)
  609.         for file in data['addfile']:
  610.             Alert.info("Remove '" + file + "'")
  611.             os.remove(file)
  612.  
  613.         data['file'] = {}
  614.         data["otherfile"] = {}
  615.         data["addfile"] = {}
  616.         data["remove_file"] = {}
  617.         data['mod'] = {}
  618.  
  619.     elif argv[1] == 'giveall':
  620.         Alert.list([mod + " version " + data['mod'][mod]['version'] + " by " + data['mod'][mod]["author"] for mod in data['mod']])
  621.  
  622.     elif argv[1] == 'valid_mod':
  623.         if argc < 3:
  624.             Alert.error("You must give a mod name", data)
  625.  
  626.         mod = loadjsonmod(MOD_PATH + argv[2] + '.json')
  627.         if mod and checkMod(mod, data):
  628.             Alert.info("Mod is valid")
  629.         else:
  630.             Alert.info("Mod isn't valid!")
  631.  
  632.     elif argv[1] == "info":
  633.         if argc < 3:
  634.             Alert.error("You must give a mod name", data)
  635.  
  636.         Alert.show = False
  637.         mod = loadjsonmod(MOD_PATH + argv[2] + '.json')
  638.         Alert.show = True
  639.  
  640.         if not mod:
  641.             Alert.info("Impossible to load mod.")
  642.         else:
  643.             Alert.info(mod["name"] + " version " + mod["version"] + " by " + mod["author"])
  644.             Alert.list(["\t" + line for line in mod["info"].split("\n")])
  645.         Alert.info()
  646.  
  647.     else:
  648.         Alert.info ("Unknow command " + argv[1])
  649.  
  650.     quit(data)
  651.  
  652. if __name__ == '__main__':
  653.     main(len(sys.argv), sys.argv)
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top