dlc.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """DWC Network Server Emulator
  2. Copyright (C) 2014 polaris-
  3. Copyright (C) 2014 ToadKing
  4. Copyright (C) 2016 Sepalani
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU Affero General Public License as
  7. published by the Free Software Foundation, either version 3 of the
  8. License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU Affero General Public License for more details.
  13. You should have received a copy of the GNU Affero General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. """
  16. import os
  17. import random
  18. import time
  19. from gamespy.gs_database import GamespyDatabase
  20. # If a game from this list requests a file listing, the server will return
  21. # that only one exists and return a random one.
  22. # This is used for Mystery Gift distribution on Generation 4 Pokemon games
  23. gamecodes_return_random_file = [
  24. 'ADAD',
  25. 'ADAE',
  26. 'ADAF',
  27. 'ADAI',
  28. 'ADAJ',
  29. 'ADAK',
  30. 'ADAS',
  31. 'CPUD',
  32. 'CPUE',
  33. 'CPUF',
  34. 'CPUI',
  35. 'CPUJ',
  36. 'CPUK',
  37. 'CPUS',
  38. 'IPGD',
  39. 'IPGE',
  40. 'IPGF',
  41. 'IPGI',
  42. 'IPGJ',
  43. 'IPGK',
  44. 'IPGS'
  45. ]
  46. filter_bit_g5 = {
  47. 'A': 0x100000,
  48. 'B': 0x200000,
  49. 'D': 0x400000,
  50. 'E': 0x800000
  51. }
  52. def get_file_count(data):
  53. return sum(1 for line in data.splitlines() if line)
  54. def filter_list(data, attr1=None, attr2=None, attr3=None,
  55. num=None, offset=None):
  56. """Filter the list based on the attribute fields.
  57. If nothing matches, at least return a newline.
  58. Pokemon BW at least expects this and will error without it.
  59. """
  60. if attr1 is None and attr2 is None and attr3 is None and \
  61. num is None and offset is None:
  62. # Nothing to filter, just return the input data
  63. return data
  64. def attrs(data):
  65. """Filter attrs."""
  66. def nc(a, b):
  67. """Filter nc."""
  68. return a is None or a == b
  69. return \
  70. len(data) == 6 and \
  71. nc(attr1, data[2]) and \
  72. nc(attr2, data[3]) and \
  73. nc(attr3, data[4])
  74. output = filter(lambda line: attrs(line.split("\t")), data.splitlines())
  75. if offset is not None:
  76. output = output[offset:]
  77. if num is not None:
  78. output = output[:num]
  79. return '\r\n'.join(output) + '\r\n'
  80. def filter_list_random_files(data, count):
  81. """Get [count] random files from the filelist."""
  82. samples = random.sample(data.splitlines(), count)
  83. return '\r\n'.join(samples) + '\r\n'
  84. def filter_list_by_date(data, token):
  85. """Allow user to control which file to receive by setting
  86. the local date selected file will be the one at
  87. index (day of year) mod (file count)."""
  88. try:
  89. userData = GamespyDatabase().get_nas_login(token)
  90. date = time.strptime(userData['devtime'], '%y%m%d%H%M%S')
  91. files = data.splitlines()
  92. ret = files[(int(date.tm_yday) - 1) % len(files)] + '\r\n'
  93. except:
  94. ret = filter_list_random_files(data, 1)
  95. return ret
  96. def filter_list_g5_mystery_gift(data, rhgamecd):
  97. """Custom selection for generation 5 mystery gifts, so that the random
  98. or data-based selection still works properly."""
  99. if len(rhgamecd) < 2 or rhgamecd[2] not in filter_bit_g5:
  100. # unknown game, can't filter
  101. return data
  102. filter_bit = filter_bit_g5[rhgamecd[2]]
  103. output = []
  104. for line in data.splitlines():
  105. attrs = line.split('\t')
  106. if len(attrs) < 3:
  107. continue
  108. line_bits = int(attrs[3], 16)
  109. if line_bits & filter_bit == filter_bit:
  110. output.append(line)
  111. return '\r\n'.join(output) + '\r\n'
  112. def safeloadfi(dlc_path, name, mode='rb'):
  113. """safeloadfi : string -> string
  114. Safely load contents of a file, given a filename,
  115. and closing the file afterward.
  116. """
  117. try:
  118. with open(os.path.join(dlc_path, name), mode) as f:
  119. return f.read()
  120. except:
  121. return None
  122. def download_count(dlc_path, post):
  123. """Handle download count request."""
  124. if post["gamecd"] in gamecodes_return_random_file:
  125. return "1"
  126. if os.path.exists(dlc_path):
  127. attr1 = post.get("attr1", None)
  128. attr2 = post.get("attr2", None)
  129. attr3 = post.get("attr3", None)
  130. if os.path.isfile(os.path.join(dlc_path, "_list.txt")):
  131. dlc_file = safeloadfi(dlc_path, "_list.txt")
  132. ls = filter_list(dlc_file, attr1, attr2, attr3)
  133. return "{}".format(get_file_count(ls))
  134. elif attr1 is None and attr2 is None and attr3 is None:
  135. return "{}".format(len(os.listdir(dlc_path)))
  136. return "0"
  137. def download_size(dlc_path, name):
  138. """Return download filename and size.
  139. Used in download list.
  140. """
  141. return (name, str(os.path.getsize(os.path.join(dlc_path, name))))
  142. def download_list(dlc_path, post):
  143. """Handle download list request.
  144. Look for a list file first. If the list file exists, send the
  145. entire thing back to the client.
  146. """
  147. # Get list file
  148. if not os.path.exists(dlc_path):
  149. return "\r\n"
  150. elif os.path.isfile(os.path.join(dlc_path, "_list.txt")):
  151. list_data = safeloadfi(dlc_path, "_list.txt") or "\r\n"
  152. else:
  153. # Doesn't have _list.txt file
  154. try:
  155. ls = [
  156. download_size(dlc_path, name)
  157. for name in sorted(os.listdir(dlc_path))
  158. ]
  159. list_data = "\r\n".join("\t\t\t\t\t".join(f) for f in ls) + "\r\n"
  160. except:
  161. return "\r\n"
  162. attr1 = post.get("attr1", None)
  163. attr2 = post.get("attr2", None)
  164. attr3 = post.get("attr3", None)
  165. if post["gamecd"].startswith("IRA") and attr1.startswith("MYSTERY"):
  166. # Pokemon BW Mystery Gifts, until we have a better solution for that
  167. ret = filter_list(list_data, attr1, attr2, attr3)
  168. ret = filter_list_g5_mystery_gift(ret, post["rhgamecd"])
  169. return filter_list_by_date(ret, post["token"])
  170. elif post["gamecd"] in gamecodes_return_random_file:
  171. # Pokemon Gen 4 Mystery Gifts, same here
  172. ret = filter_list(list_data, attr1, attr2, attr3)
  173. return filter_list_by_date(ret, post["token"])
  174. else:
  175. # Default case for most games
  176. num = post.get("num", None)
  177. if num is not None:
  178. num = int(num)
  179. offset = post.get("offset", None)
  180. if offset is not None:
  181. offset = int(offset)
  182. return filter_list(list_data, attr1, attr2, attr3, num, offset)
  183. def download_contents(dlc_path, post):
  184. """Handle download contents request.
  185. Get only the base filename just in case there is a path involved
  186. somewhere in the filename string.
  187. """
  188. contents = os.path.basename(post["contents"])
  189. return safeloadfi(dlc_path, contents)