import cherrypy
import hashlib
import os
import pathlib
import requests
import shelve
import shutil
content_host = "https://cadence.moe"
db = None
def init_database():
global db
db = shelve.open("crumpet_server_config", writeback=False)
db.setdefault("game_directory", None)
db.setdefault("customlevels_directory", None)
db.setdefault("is_installed", True)
try:
init_database()
except:
# database corruption error? HOW??? do I actually need to close the file like it told me
# because I don't know how to close the file like it told me
print("resetting server configuration due to database corruption")
db.close()
os.unlink("crumpet_server_config")
init_database()
def hash_file(filename):
h = hashlib.sha256()
b = bytearray(h.block_size)
mv = memoryview(b)
with open(filename, "rb") as file:
for n in iter(lambda: file.readinto(mv), 0):
h.update(mv[:n])
return h
def check_game_path(game_path):
if not game_path.exists() or not game_path.is_dir():
return {
"status": "failure",
"message": "Directory does not exist."
}
res2_path = game_path.joinpath("res2.dat")
if not res2_path.exists() or res2_path.is_dir():
return {
"status": "failure",
"message": "Directory exists, but does not contain res2.dat."
}
return None
def open_level(filename, mode):
return open(os.path.join(db["customlevels_directory"], filename), mode)
def restricted_message():
return "This action is restricted. You can enable this action by installing Crumpet on your own computer."
def autodetect_game_directory():
return None # placeholder
class Crumpet:
def _cp_dispatch(self, vpath):
if vpath[:2] == ["api", "rtw"]:
vpath[:2] = []
return self
return vpath
@cherrypy.expose
@cherrypy.tools.json_out()
def get_paths(self):
# Check game directory
game_directory = db["game_directory"]
if game_directory:
game_path = pathlib.Path(game_directory)
problem = check_game_path(game_path)
if problem:
game_directory = autodetect_game_directory()
# do not need to check customlevels directory since the interface should replace whatever is sent.
customlevels_directory = db["customlevels_directory"]
return {
"game_directory": game_directory,
"customlevels_directory": customlevels_directory
}
@cherrypy.expose
@cherrypy.tools.json_in()
@cherrypy.tools.json_out()
def save_game_directory(self):
try:
root = cherrypy.request.json
except AttributeError:
return {
"status": "failure",
"message": "Requires JSON POST input."
}
if type(root.get("value")) != str:
return {
"status": "failure",
"message": "Requires `value` string parameter in JSON."
}
game_directory = root["value"]
# Check that the game is there
game_path = pathlib.Path(game_directory)
if not game_path.exists() or not game_path.is_dir():
return {
"status": "failure",
"message": "Directory does not exist."
}
res2_path = game_path.joinpath("res2.dat")
if not res2_path.exists() or res2_path.is_dir():
return {
"status": "failure",
"message": "Directory exists, but does not contain res2.dat."
}
db["game_directory"] = game_directory
return {
"status": "success"
}
@cherrypy.expose
@cherrypy.tools.json_in()
@cherrypy.tools.json_out()
def save_customlevels_directory(self):
try:
root = cherrypy.request.json
except AttributeError:
return {
"status": "failure",
"message": "Requires JSON POST input."
}
if type(root.get("value")) != str:
return {
"status": "failure",
"message": "Requires `value` string parameter in JSON."
}
customlevels_directory = root["value"]
# Check that the directory is there
customlevels_path = pathlib.Path(customlevels_directory)
if not customlevels_path.exists() or not customlevels_path.is_dir():
return {
"status": "failure",
"message": "Directory does not exist."
}
db["customlevels_directory"] = customlevels_directory
return {
"status": "success"
}
@cherrypy.expose
@cherrypy.tools.json_out()
def request_images(self, *path_components):
# Request a token
token_request = requests.get(content_host + "/api/rtw/token")
token = token_request.content
# print("token is: {}".format(token))
# Hash the file
game_directory = db["game_directory"]
res2_path = pathlib.Path(game_directory).joinpath("res2.dat")
if not res2_path.exists() or res2_path.is_dir():
return {
"status": "failure",
"message": "Could not locate res2.dat."
}
h = hash_file(res2_path.absolute())
# print("original hash is: {}".format(h.hexdigest()))
# Update the hash with the token
h.update(token)
patched_hash = h.hexdigest()
# print("patched hash is: {}".format(patched_hash))
# Ask for images
download_url = content_host + "/api/rtw/fetchimages?token={}&hash={}".format(token.decode(), patched_hash)
images_request = requests.get(download_url)
if images_request.status_code != 200:
return {
"status": "failure",
"message": images_request.content
}
output_filename = pathlib.Path(__file__).parent.joinpath("images.zip").absolute()
with open(output_filename, "wb") as fd:
for chunk in images_request.iter_content(128):
fd.write(chunk)
unpack_directory = pathlib.Path(__file__).parent.joinpath("root").absolute()
shutil.unpack_archive(output_filename, unpack_directory)
return {
"status": "success"
}
@cherrypy.expose
@cherrypy.tools.json_in()
def download(self):
if not db["is_installed"]:
return restricted_message()
root = cherrypy.request.json
level_data = bytearray(root["data"])
filename = root["filename"]
with open_level(filename, "wb") as fd:
fd.write(level_data)
cherrypy.response.status = 204
@cherrypy.expose
@cherrypy.tools.json_out()
def load(self, /, filename):
with open_level(filename, "rb") as fd:
level_data = fd.read()
return list(level_data)
server_root = pathlib.Path(__file__).parent.joinpath("root")
cherrypy.config.update({"server.socket_port": 3456, "server.socket_host": "0.0.0.0"})
cherrypy.quickstart(Crumpet(), "/", {
"/": {
"tools.staticdir.on": True,
"tools.staticdir.dir": str(server_root.absolute()),
"tools.staticdir.index": "index.html"
}
})