7163D

c3mm.py

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

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×