SHARE
TWEET

c3mm.py

7163D Oct 8th, 2016 97 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
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