├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── Channels
├── Check_Mii_Out_Channel
│ ├── DebugTools
│ │ ├── TestUpdate.py
│ │ ├── addToList.py
│ │ ├── generateCountries.py
│ │ ├── quicklist.py
│ │ └── reset.py
│ ├── ForceUpdate.sh
│ ├── bestlist.py
│ ├── cmoc.py
│ ├── condetail.py
│ ├── config.json.example
│ ├── coninfo.py
│ ├── conmail.py
│ ├── contestCLI.py
│ ├── entrylist.py
│ ├── htmlcontest.py
│ ├── htmlpopular.py
│ ├── htmlrankings.py
│ ├── htmltop50.py
│ ├── jobs.cron
│ ├── miikaitai.py
│ ├── numberinfo.py
│ ├── popcrafts.py
│ ├── popular.py
│ ├── randlist.py
│ ├── sign_encrypt.py
│ ├── top50.py
│ └── wiisports.py
├── Everybody_Votes_Channel
│ ├── __init__.py
│ ├── config.json.example
│ ├── jobs.cron
│ ├── votes.py
│ └── voteslists.py
├── Forecast_Channel
│ ├── README.md
│ ├── __init__.py
│ ├── config.json.example
│ ├── forecast.py
│ ├── forecastlists.py
│ ├── forecastregions.py
│ └── jobs.cron
├── News_Channel
│ ├── README.md
│ ├── __init__.py
│ ├── config.json.example
│ ├── jobs.cron
│ ├── logos
│ │ ├── AFP_French.jpg
│ │ ├── AFP_German.jpg
│ │ ├── AFP_Spanish.jpg
│ │ ├── ANP.jpg
│ │ ├── AP.jpg
│ │ ├── CanadianPress.jpg
│ │ ├── Reuters.jpg
│ │ └── SID.jpg
│ ├── news.py
│ ├── newsdownload.py
│ └── newsmake.py
├── Nintendo_Channel
│ ├── __init__.py
│ ├── config.json.example
│ ├── dllist.py
│ ├── dstrial_header.py
│ ├── info.py
│ ├── ninch_thumb.py
│ ├── ninfile.py
│ ├── ninfile1.py
│ ├── ninfile2.py
│ ├── ninfile3.py
│ └── ratings
│ │ ├── BBFC
│ │ ├── 12.jpg
│ │ ├── 15.jpg
│ │ ├── 18.jpg
│ │ ├── PG.jpg
│ │ └── U.jpg
│ │ ├── CERO
│ │ ├── A.jpg
│ │ ├── B.jpg
│ │ ├── C.jpg
│ │ ├── D.jpg
│ │ ├── Z.jpg
│ │ ├── education.jpg
│ │ └── scheduled.jpg
│ │ ├── ESRB
│ │ ├── E-small.jpg
│ │ ├── E.jpg
│ │ ├── E10-small.jpg
│ │ ├── E10.jpg
│ │ ├── EC.jpg
│ │ ├── M.jpg
│ │ ├── T.jpg
│ │ ├── maycontain.jpg
│ │ └── visitesrb.jpg
│ │ └── PEGI
│ │ ├── .DS_Store
│ │ ├── 12.jpg
│ │ ├── 16.jpg
│ │ ├── 18.jpg
│ │ ├── 3.jpg
│ │ └── 7.jpg
└── __init__.py
├── Games
└── My_Aquarium
│ └── myaquarium.py
├── LICENSE
├── README.md
├── Tools
├── Forecast
│ └── parse.py
├── LZ Tools
│ ├── DSDecmp
│ │ ├── DSDecmp.exe
│ │ └── Plugins
│ │ │ ├── DSDecmp.xml
│ │ │ ├── GoldenSunDD.dll
│ │ │ └── LuminousArc.dll
│ └── lzss
│ │ ├── lzss
│ │ ├── lzss-mac
│ │ └── lzss.exe
├── Sign_Encrypt
│ └── sign_encrypt.py
└── Time
│ └── convert.py
├── requirements.txt
└── utils.py
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # RiiConnect24 Issue Template
2 | # Please delete all lines starting with # before submitting, this is just help text.
3 |
4 | # Please note issues regarding sign.py and config.py will not be read: those two files are RiiConnect24 Development's private property, and are kept secret for a reason.
5 |
6 | Short Description of Issue:
7 |
8 | Long Issue:
9 |
10 | Steps to Reproduce :
11 | -
12 | -
13 | -
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # RiiConnect24 Pull Request Template
2 |
3 | Please ensure your code works! It needs to be able to at least make files before we accept PRs.
4 | Please also ensure no config.py change has occured; it needs to be able to read our test configs for us to verify the code works.
5 |
6 | Author Name for Credits: FIRSTNAME (LASTNAME)
7 |
8 | Short Description: DESC.
9 |
10 | Long Description: DESCRIPTION.
11 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "master" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "master" ]
20 | schedule:
21 | - cron: '0 5 * * 5'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # General
2 |
3 | .DS_Store
4 | *.pem
5 | *.pyc
6 | config.py
7 | config.json
8 | .DS_Store
9 |
10 | # Forecast Channel
11 |
12 | forecast.*
13 | short.*
14 |
15 | # News Channel
16 |
17 | newstime
18 | news.bin.*
19 |
20 | # Everybody Votes Channel
21 |
22 | voting.*
23 | *_q.*
24 | *_r.*
25 |
26 |
27 | # JetBrains IDE
28 |
29 | .idea/**
30 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/DebugTools/TestUpdate.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from os import system
3 |
4 | # temporary until we set up cron
5 |
6 | while True:
7 | system("./ForceUpdate.sh")
8 | sleep(1800)
9 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/DebugTools/addToList.py:
--------------------------------------------------------------------------------
1 | from cmoc import ResetList, QuickList, Prepare
2 |
3 | # example script gets every single mii in the DB, then adds it to spot_list
4 |
5 | ql = QuickList()
6 | pr = Prepare()
7 |
8 | miilist = []
9 | artisanlist = []
10 | miidata = "gAoAPwAAAAAAAAAAAAAAAAAAAAAAAF4AhonbB8JJnRIgBDxAuX0ookiKBEAAMZkEAIoAiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAAaLw=="
11 | likes = 0
12 | skill = 0
13 | country = 49
14 | initial = "AA"
15 |
16 | artisandata = "gAsAUABlAGUAdwBlAGUAAAAAAAAAAAAAhorkD1RU1sYgADxAub0IPAiQCEAUabiQAIoAiiUEAAAAAAAAAAAAAAAAAAAAAAAAAAC68Q=="
17 | master = 0
18 |
19 | ResetList(b"NL")
20 | for i in range(499):
21 | miilist.append(
22 | (i, initial, likes, skill, country, miidata) + (artisandata, i, master)
23 | )
24 |
25 | list_type = "NL"
26 |
27 | data = ql.build(list_type, miilist)
28 |
29 | with open("150/new_list01.ces", "wb") as file:
30 | file.write(pr.prepare(data))
31 |
32 | with open("150/new_list01.dec", "wb") as file:
33 | file.write(data)
34 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/DebugTools/generateCountries.py:
--------------------------------------------------------------------------------
1 | from cmoc import Prepare
2 | from struct import pack
3 |
4 | # automatically generates all country codes for /first. sloppy code because it only needs to be ran once
5 |
6 | # codes = [1, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 128, 136, 144, 145, 152, 153, 154, 155, 156, 160, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177]
7 | # codes = [201, 202, 203, 204, 205, 206]
8 | codes = [100]
9 | p = Prepare()
10 |
11 |
12 | def u32(data):
13 | if not 0 <= data <= 4294967295:
14 | log("u32 out of range: %s" % data, "INFO")
15 | data = 0
16 | return pack(">I", data)
17 |
18 |
19 | for i in codes:
20 | with open(("addition/" + str(i) + ".ces"), "wb") as file:
21 | # data = bytes.fromhex('46440000') + u32(i) + bytes.fromhex('00000000000000000000000000000000FFFFFFFFFFFFFFFF4644000C0000000196000003')
22 | data = (
23 | bytes.fromhex("4144000000000000")
24 | + u32(i)
25 | + bytes.fromhex("000000000000009600000096FFFFFFFFFFFFFFFF")
26 | )
27 | file.write(p.prepare(data))
28 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/DebugTools/quicklist.py:
--------------------------------------------------------------------------------
1 | from cmoc import QuickList, Prepare
2 | import MySQLdb
3 | from json import load
4 |
5 | with open("./config.json", "r") as f:
6 | config = load(f)
7 |
8 | # example script gets every single mii in the DB, then adds it to spot_list
9 |
10 | ql = QuickList()
11 | pr = Prepare()
12 |
13 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "cmoc")
14 | cursor = db.cursor()
15 |
16 | cursor.execute("SELECT craftsno,entryno FROM mii ORDER BY permlikes")
17 | numbers = cursor.fetchall()
18 |
19 | miilist = []
20 | artisanlist = []
21 |
22 | for i in range(
23 | len(numbers)
24 | ): # add the artisan data to each mii based on their craftsno
25 | cursor.execute(
26 | "SELECT entryno,initial,permlikes,skill,country,miidata FROM mii WHERE craftsno = %s AND entryno = %s ORDER BY permlikes",
27 | (numbers[i][0], numbers[i][1]),
28 | )
29 | mii = cursor.fetchone()
30 | cursor.execute(
31 | "SELECT miidata,craftsno,master FROM artisan WHERE craftsno = %s",
32 | [numbers[i][0]],
33 | )
34 | artisan = cursor.fetchone()
35 | miilist.append(mii + artisan)
36 |
37 | list_type = "SL"
38 |
39 | data = ql.build(list_type, miilist)
40 |
41 | with open("{}/150/spot_list.ces".format(config["miicontest_path"]), "wb") as file:
42 | file.write(pr.prepare(data))
43 |
44 | with open("{}/150/spot_list.dec".format(config["miicontest_path"]), "wb") as file:
45 | file.write(data)
46 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/DebugTools/reset.py:
--------------------------------------------------------------------------------
1 | from sys import argv
2 | from cmoc import ResetList
3 |
4 | try:
5 | listname = argv[1].upper().encode() # converts to uppercase, then passed as bytes
6 | ResetList(listname)
7 |
8 | except IndexError as error:
9 | print("No list provided! Usage: python3 ./reset.py [type]")
10 | print("Available types are SL, PL, RL, NL, RL")
11 | print("This will remove all miis in the specified list!")
12 |
13 | except ValueError as error:
14 | print("Invalid List! Usage: python3 ./reset.py [type]")
15 | print("Available types are SL, PL, RL, NL, RL")
16 | print("This will remove all miis in the specified list!")
17 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/ForceUpdate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python3.7 ./top50.py
4 | python3.7 ./popcrafts.py
5 | python3.7 ./popular.py
6 | python3.7 ./randlist.py
7 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/bestlist.py:
--------------------------------------------------------------------------------
1 | from cmoc import BestList, Prepare
2 | import MySQLdb
3 | from json import load
4 |
5 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
6 | config = load(f)
7 |
8 | db = MySQLdb.connect(
9 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
10 | )
11 | cursor = db.cursor()
12 | bl = BestList()
13 | pr = Prepare()
14 |
15 | cursor.execute("SELECT id FROM contests WHERE status = 'results'")
16 | ids = cursor.fetchall()
17 |
18 | for id in ids:
19 | id = id[0]
20 | cursor.execute(
21 | "SELECT conmiis.entryno, conmiis.craftsno, conmiis.miidata, artisan.miidata, artisan.country, artisan.master FROM conmiis, artisan WHERE conmiis.craftsno = artisan.craftsno AND conmiis.contest = %s ORDER BY conmiis.likes DESC LIMIT 50",
22 | [id],
23 | )
24 | miis = cursor.fetchall()
25 |
26 | build = bl.build(id, miis)
27 |
28 | with open(
29 | "{}/contest/{}/best_list.ces".format(config["miicontest_path"], id), "wb"
30 | ) as file:
31 | file.write(pr.prepare(build))
32 |
33 | with open("decfiles/contests/{}/best_list.dec".format(id), "wb+") as file:
34 | file.write(build)
35 |
36 | db.close()
37 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/condetail.py:
--------------------------------------------------------------------------------
1 | from cmoc import ConDetail, Prepare, Thumbnail, Photo
2 | import MySQLdb
3 | from json import load
4 | from os import path, makedirs
5 | import requests
6 |
7 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
8 | config = load(f)
9 |
10 | db = MySQLdb.connect(
11 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
12 | )
13 | cursor = db.cursor()
14 | cd = ConDetail()
15 | pr = Prepare()
16 | th = Thumbnail()
17 | ph = Photo()
18 |
19 | cursor.execute(
20 | "SELECT id, start, end, status, entrycount, topic_japanese, topic, topic_german, topic_french, topic_spanish, topic_italian, topic_dutch, description_japanese, description, description_german, description_french, description_spanish, description_italian, description_dutch, thumbnail_url, souvenir_url FROM contests WHERE status != 'closed' AND status != 'waiting'"
21 | )
22 | contests = cursor.fetchall()
23 |
24 | for i in contests:
25 | id = i[0]
26 | start = i[1]
27 | end = i[2]
28 | status = i[3]
29 | entrycount = i[4] - i[4] % 10 # must be rounded down to nearest 10
30 | topics = [i[5], i[6], i[7], i[8], i[9], i[10], i[11]]
31 | descriptions = [i[12], i[13], i[14], i[15], i[16], i[17], i[18]]
32 | thumbnail_url = i[19]
33 | souvenir_url = i[20]
34 |
35 | for language in range(0, 7):
36 | if not topics[language] or not descriptions[language]:
37 | continue
38 |
39 | data = cd.build(id, start, end, status, entrycount, topics[language], descriptions[language])
40 |
41 | if not path.exists("{}/contest/{}".format(config["miicontest_path"], id)):
42 | makedirs("{}/contest/{}".format(config["miicontest_path"], id))
43 |
44 | with open(
45 | "{}/contest/{}/con_detail{}.ces".format(config["miicontest_path"], id, language), "wb"
46 | ) as file:
47 | file.write(pr.prepare(data))
48 |
49 | if not path.exists("decfiles/contests/{}".format(id)):
50 | makedirs("decfiles/contests/{}".format(id))
51 |
52 | with open("decfiles/contests/{}/con_detail{}.dec".format(id, language), "wb+") as file:
53 | file.write(data)
54 |
55 | if path.exists("/var/rc24/thumbnail/{}.jpg".format(id)) or thumbnail_url:
56 | if thumbnail_url:
57 | thumbnail = requests.get(thumbnail_url).content
58 |
59 | with open("/var/rc24/thumbnail/{}.jpg".format(id), "wb") as f:
60 | f.write(thumbnail)
61 |
62 | data = th.build(id)
63 |
64 | if not path.exists("{}/contest/{}".format(config["miicontest_path"], id)):
65 | makedirs("{}/contest/{}".format(config["miicontest_path"], id))
66 |
67 | with open(
68 | "{}/contest/{}/thumbnail.ces".format(config["miicontest_path"], id), "wb"
69 | ) as file:
70 | file.write(pr.prepare(data))
71 |
72 | if not path.exists("decfiles/thumbnails/{}".format(id)):
73 | makedirs("decfiles/thumbnails/{}".format(id))
74 |
75 | with open("decfiles/thumbnails/{}/thumbnail.dec".format(id), "wb+") as file:
76 | file.write(data)
77 |
78 | if path.exists("/var/rc24/souvenir/{}.jpg".format(id)):
79 | if souvenir_url:
80 | souvenir = requests.get(souvenir_url).content
81 |
82 | with open("/var/rc24/souvenir/{}.jpg".format(id), "wb") as f:
83 | f.write(souvenir)
84 |
85 | data = ph.build(id)
86 |
87 | with open(
88 | "{}/contest/{}/photo.ces".format(config["miicontest_path"], id), "wb"
89 | ) as file:
90 | file.write(pr.prepare(data))
91 |
92 | if not path.exists("decfiles/souvenirs/{}".format(id)):
93 | makedirs("decfiles/souvenirs/{}".format(id))
94 |
95 | with open("decfiles/souvenirs/{}/photo.dec".format(id), "wb+") as file:
96 | file.write(data)
97 |
98 |
99 | db.close()
100 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "file_path": "/path/to/save/file",
3 | "lzss_path": "/path/to/lzss",
4 | "production": true,
5 | "miicontest_path": "/var/www/wapp.wii.com/miicontest/public_html",
6 | "miicontestp_path": "/var/www/wapp.wii.com/miicontestp/public_html",
7 | "dbuser": "mysqluser",
8 | "dbpass": "mysqlpassword",
9 | }
10 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/coninfo.py:
--------------------------------------------------------------------------------
1 | from cmoc import ConInfo, Prepare
2 | import MySQLdb
3 | from json import load
4 | from time import mktime
5 | from datetime import datetime
6 | from subprocess import call
7 |
8 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
9 | config = load(f)
10 |
11 |
12 | def setRankings(id): # once voting is concluded, rank miis from 1 to 10
13 | cursor.execute(
14 | "UPDATE conmiis SET likes = (SELECT COUNT(*) FROM convotes WHERE id = %s AND (craftsno1 = conmiis.craftsno OR craftsno2 = conmiis.craftsno OR craftsno3 = conmiis.craftsno)) WHERE contest = %s",
15 | (id, id),
16 | ) # adds all the mii's votes
17 | db.commit()
18 | cursor.execute(
19 | "SELECT entryno FROM conmiis WHERE contest = %s ORDER BY likes ASC", [id]
20 | )
21 | miis = cursor.fetchall()
22 | miiCount = len(miis)
23 | for i in range(miiCount):
24 | # idk how this works, but it does
25 | percentile = round((i / 10) / miiCount * 90) + 1
26 | cursor.execute(
27 | "UPDATE conmiis SET `rank` = %s WHERE entryno = %s",
28 | (percentile, miis[i][0]),
29 | )
30 |
31 |
32 | currentTime = int(mktime(datetime.utcnow().timetuple())) - 946684800
33 | db = MySQLdb.connect(
34 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
35 | )
36 | cursor = db.cursor()
37 | ci = ConInfo()
38 | pr = Prepare()
39 |
40 | cursor.execute(
41 | "SELECT id, start, end, status, description FROM contests WHERE status != 'closed'"
42 | )
43 |
44 | new = False
45 |
46 | for con in cursor.fetchall():
47 | id = con[0]
48 | start = con[1]
49 | end = con[2]
50 | status = con[3]
51 |
52 | if (
53 | status == "waiting" and start <= currentTime and end >= currentTime
54 | ): # contest is ready to be opened
55 | cursor.execute("UPDATE contests SET status = 'open' WHERE id = %s", [id])
56 |
57 | new = True
58 |
59 | elif (
60 | start <= currentTime and end <= currentTime
61 | ): # contest is ready to move to its next status
62 | # end time is increased by 7 days unless contest closes
63 |
64 | if status == "open":
65 | endTime = int(end + (7 * 24 * 60 * 60))
66 | cursor.execute("SELECT COUNT(*) FROM conmiis WHERE contest = %s", [id])
67 | cursor.execute(
68 | "UPDATE contests SET status = 'judging', end = %s, entrycount = %s, sent = 0 WHERE id = %s",
69 | (endTime, cursor.fetchone()[0], id),
70 | )
71 |
72 | elif status == "judging":
73 | endTime = int(end + (30 * 24 * 60 * 60))
74 | cursor.execute(
75 | "UPDATE contests SET status = 'results', end = %s, sent = 0 WHERE id = %s",
76 | (endTime, id),
77 | )
78 |
79 | elif status == "results":
80 | cursor.execute("UPDATE contests SET status = 'closed' WHERE id = %s", [id])
81 |
82 | new = True
83 | else:
84 | setRankings(id)
85 |
86 |
87 | db.commit()
88 |
89 | cursor.execute(
90 | "SELECT id, status FROM contests WHERE status != 'closed' AND status != 'waiting'"
91 | )
92 | contests = cursor.fetchall()
93 |
94 | data = ci.build(contests)
95 |
96 | with open("{}/150/con_info.ces".format(config["miicontest_path"]), "wb") as file:
97 | file.write(pr.prepare(data))
98 |
99 | with open("decfiles/con_info.dec", "wb") as file:
100 | file.write(data)
101 |
102 | db.close()
103 |
104 | if new:
105 | call(["python3", "/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/conmail.py"])
106 |
107 | call(["python3", "/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/condetail.py"])
108 | call(["python3", "/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/bestlist.py"])
109 | call(["python3", "/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/entrylist.py"])
110 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/conmail.py:
--------------------------------------------------------------------------------
1 | import calendar
2 | import MySQLdb
3 | import time
4 | from base64 import encodebytes
5 | from datetime import datetime
6 | from io import BytesIO
7 | from json import load
8 | from os import system, remove, path, makedirs
9 | from PIL import Image
10 | from random import randint
11 | from requests import post
12 |
13 | from textwrap import wrap
14 |
15 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
16 | config = load(f)
17 |
18 | db = MySQLdb.connect(
19 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
20 | )
21 | cursor = db.cursor()
22 |
23 | cursor.execute(
24 | "SELECT id, status, description, sent, photo_url FROM contests WHERE (status != 'closed' AND status != 'waiting')"
25 | )
26 | contests = cursor.fetchall()
27 |
28 | now = datetime.utcnow()
29 | boundary = now.strftime("--BoundaryForDL%Y%m%d%H%m/" + str(randint(1000000, 9999999)))
30 | date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
31 | message = "{}\r\nContent-Type: text/plain\r\n\r\nThis part is ignored.\r\n\r\n\r\n\r\n".format(
32 | boundary
33 | )
34 |
35 |
36 | def enc(text, description):
37 | return str(
38 | encodebytes(text.format(description).encode("utf-16be"))
39 | .replace(b"\n", b"\r\n")
40 | .decode("utf-8")
41 | )
42 |
43 |
44 | def convert_to_baseline(path):
45 | """If for some reason the image has an alpha channel (probably a PNG), fill the background with white."""
46 |
47 | with open(path, "rb") as f:
48 | picture = f.read()
49 | f.close()
50 |
51 | image = Image.open(BytesIO(picture))
52 |
53 | image = image.convert("RGB")
54 |
55 | data = list(image.getdata())
56 | image_without_exif = Image.new(image.mode, image.size)
57 | image_without_exif.putdata(data)
58 |
59 | buffer = BytesIO()
60 | image_without_exif.save(buffer, format="jpeg")
61 |
62 | return str(encodebytes(buffer.getvalue()).replace(b"\n", b"\r\n").decode("utf-8"))
63 |
64 |
65 | i = 0
66 |
67 | if contests == ():
68 | exit()
69 |
70 | # get the con_detail stuff which will show on the Wii Menu icon
71 |
72 | sorted_contests = []
73 |
74 | for c in contests:
75 | sorted_contests.append(c[0])
76 |
77 | contest_id = max(sorted_contests)
78 |
79 | decfilename = "/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/decfiles/contests/{}/con_detail1.dec".format(
80 | str(contest_id)
81 | )
82 |
83 | if path.exists(decfilename):
84 | boundary2 = "----=_CMOC_Contest_Details"
85 |
86 | with open(decfilename, "rb") as f:
87 | con_detail = str(encodebytes(f.read()).replace(b"\n", b"\r\n").decode("utf-8"))
88 |
89 | thumbnail_path = "/var/rc24/thumbnail/{}".format(str(contest_id))
90 |
91 | message += (
92 | boundary
93 | + "\r\n"
94 | + "Content-Type: text/plain\r\n\r\n"
95 | + "Date: {}\r\n".format(date)
96 | + "From: w9999999900000000@rc24.xyz\r\n"
97 | + "To: allusers@rc24.xyz\r\n"
98 | + "Message-ID: <776DCLBHYHD.2QBO4Y3I2Y04S@JavaMail.w9999999900000000@rc24.xyz>\r\n"
99 | + "Subject: \r\n"
100 | + "MIME-Version: 1.0\r\n"
101 | + 'Content-Type: multipart/mixed; boundary="{}"\r\n'.format(boundary2)
102 | + "Content-Transfer-Encoding: base64\r\n"
103 | + "X-Wii-AppID: 3-48415041-3031\r\n"
104 | + "X-Wii-Tag: 00000001\r\n"
105 | + "X-Wii-Cmd: 00080001\r\n\r\n"
106 | + "--"
107 | + boundary2
108 | + "\r\n"
109 | + "Content-Type: application/octet-stream;\r\n"
110 | + " name=con_detail1.bin\r\n"
111 | + "Content-Transfer-Encoding: base64\r\n"
112 | + "Content-Disposition: attachment;\r\n"
113 | + " filename=con_detail1.bin\r\n\r\n"
114 | + con_detail
115 | + "\r\n\r\n"
116 | )
117 |
118 | if path.exists(thumbnail_path + ".jpg"):
119 | system("convert " + thumbnail_path + ".jpg " + thumbnail_path + ".png")
120 | system("wimgt ENCODE " + thumbnail_path + ".png -x TPL.RGB565")
121 | system("mv " + thumbnail_path + " " + thumbnail_path + ".tpl")
122 |
123 | with open(thumbnail_path + ".tpl", "rb") as f:
124 | con_icon = str(
125 | encodebytes(f.read()[64:]).replace(b"\n", b"\r\n").decode("utf-8")
126 | )
127 |
128 | message += (
129 | "--"
130 | + boundary2
131 | + "\r\n"
132 | + "Content-Type: application/octet-stream;\r\n"
133 | + " name=thumbnail_"
134 | + str(contest_id)
135 | + ".tpl\r\n"
136 | + "Content-Transfer-Encoding: base64\r\n"
137 | + "Content-Disposition: attachment;\r\n"
138 | + " filename=thumbnail_"
139 | + str(contest_id)
140 | + ".tpl\r\n\r\n"
141 | + con_icon
142 | + "\r\n\r\n"
143 | )
144 |
145 | message += "--" + boundary2 + "--\r\n\r\n"
146 |
147 | # make text, photo and attachment
148 |
149 | for c in contests:
150 | i += 1
151 | contest_id = c[0]
152 | status = c[1]
153 | description = c[2]
154 | sent = c[3]
155 | photo_url = c[4]
156 |
157 | if not path.exists("/var/rc24/photo/{}.jpg".format(id)) and photo_url:
158 | if photo_url:
159 | photo = requests.get(photo_url).content
160 |
161 | with open("/var/rc24/photo/{}.jpg".format(id), "wb") as f:
162 | f.write(photo)
163 |
164 | if sent == 0:
165 | boundary2 = "----=_CMOC_Contest_{}".format(i)
166 |
167 | contest_posting = "*******************************\r\nA New Contest is Under Way\r\n*******************************\r\n\r\nCare to test your Mii-making skills\r\nby designing a Mii on a particular\r\ntheme?\r\n\r\n\u25c6Contest Theme:\r\n{}\r\n\r\n\u25c6How to Submit an Entry\r\n1. Design a Mii in the Mii\r\n Channel.\r\n2. Go to the Check Mii Out\r\n Channel and submit your Mii.\r\n\r\n\r\nP.S. Check out https://mii.rc24.xyz/,\r\nit's the official companion website\r\nfor the Check Mii Out Channel!\r\n\r\n\r\n----------------------------------\r\nThis message is regarding the\r\nCheck Mii Out Channel.\r\n\r\nIf you do not wish to receive further\r\ncommercial messages from RiiConnect24,\r\nplease click the opt-out icon on the \r\nupper-right corner of the screen.\r\n\r\nYou can opt out of either (1) \r\nmessages for the Check Mii Out\r\nChannel only or (2) all messages for\r\nall channels and games."
168 | contest_judging = "*******************************\r\nCome and Judge a Contest\r\n*******************************\r\n\r\nCome over to the Check Mii Out\r\nChannel and judge a few Miis\r\nfor a contest.\r\n\r\n\u25c6Contest Theme:\r\n{}\r\n\r\n\r\nP.S. Check out https://mii.rc24.xyz/,\r\nit's the official companion website\r\nfor the Check Mii Out Channel!\r\n\r\n\r\n----------------------------------\r\n\r\n\r\nThis message is regarding the\r\nCheck Mii Out Channel.\r\n\r\nIf you do not wish to receive further\r\ncommercial messages from RiiConnect24,\r\nplease click the opt-out icon on the \r\nupper-right corner of the screen.\r\n\r\nYou can opt out of either (1) \r\nmessages for the Check Mii Out\r\nChannel only or (2) all messages for\r\nall channels and games."
169 | contest_results = "*******************************\r\nContest Results\r\n*******************************\r\n\r\nWe've tallied up all the votes, and\r\nthe winners for this contest have\r\nbeen decided!\r\n\r\n\u25c6Contest Theme:\r\n{}\r\n\r\n\r\nP.S. Check out https://mii.rc24.xyz/,\r\nit's the official companion website\r\nfor the Check Mii Out Channel!\r\n\r\n\r\n----------------------------------\r\nThis message is regarding the\r\nCheck Mii Out Channel.\r\n\r\nIf you do not wish to receive further\r\ncommercial messages from RiiConnect24,\r\nplease click the opt-out icon on the \r\nupper-right corner of the screen.\r\n\r\nYou can opt out of either (1) \r\nmessages for the Check Mii Out\r\nChannel only or (2) all messages for\r\nall channels and games."
170 |
171 | if status == "open":
172 | contest_text = enc(contest_posting, description)
173 | contest_status = "A New Contest is Under Way\n\nCare to test your Mii-making skills\nby designing a Mii on a particular\ntheme?\n\nContest Theme: {}".format(
174 | description
175 | )
176 | elif status == "judging":
177 | contest_text = enc(contest_judging, description)
178 | contest_status = "Come and Judge a Contest\n\nCome over to the Check Mii Out\nChannel and judge a few Miis\nfor a contest.\n\nContest Theme: {}".format(
179 | description
180 | )
181 | elif status == "results":
182 | contest_text = enc(contest_results, description)
183 | contest_status = "Contest Results\n\nWe've tallied up all the votes, and\nthe winners for this contest have\nbeen decided!\n\nContest Theme: {}".format(
184 | description
185 | )
186 |
187 | message += (
188 | boundary
189 | + "\r\n"
190 | + "Content-Type: text/plain\r\n\r\n"
191 | + "Date: {}\r\n".format(date)
192 | + "From: w9999999900000000@rc24.xyz\r\n"
193 | + "To: allusers@rc24.xyz\r\n"
194 | + "Message-ID: <776DCLBHYHD.2QBO4Y3I2Y04S@JavaMail.w9999999900000000@rc24.xyz>\r\n"
195 | + "Subject: \r\n"
196 | + "MIME-Version: 1.0\r\n"
197 | + 'Content-Type: multipart/mixed; boundary="{}"\r\n'.format(boundary2)
198 | + "Content-Transfer-Encoding: base64\r\n"
199 | + "X-Wii-AltName: AEMAaABlAGMAawAgAE0AaQBpACAATwB1AHQAIABDAGgAYQBuAG4AZQBs=\r\n"
200 | + "X-Wii-MB-OptOut: 1\r\n"
201 | + "X-Wii-MB-NoReply: 1\r\n"
202 | + "X-Wii-AppID: 3-48415041-3031\r\n\r\n"
203 | + "--"
204 | + boundary2
205 | + "\r\n"
206 | + "Content-Type: text/plain; charset=utf-16BE\r\n"
207 | + "Content-Transfer-Encoding: base64\r\n\r\n"
208 | + contest_text
209 | + "\r\n\r\n\r\n\r\n\r\n"
210 | + "--"
211 | + boundary2
212 | + "\r\n"
213 | + "Content-Type: application/x-wii-msgboard;\r\n"
214 | + " name=cmoc_letterform.arc\r\n"
215 | + "Content-Transfer-Encoding: base64\r\n"
216 | + "Content-Disposition: attachment;\r\n"
217 | + " filename=cmoc_letterform.arc\r\n\r\n"
218 | + "Vao4LQAAACAAAAAkAAAAYAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAIAAAABAAAAYAAAACMA\r\n"
219 | + "Y2hqdW1wLmJpbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ2hKcAAAACMAAAABAAAAAAAB\r\n"
220 | + "AAFIQVBBAAAAIAAAAANhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n\r\n"
221 | )
222 |
223 | photo_path = "/var/rc24/photo/{}.jpg".format(contest_id)
224 |
225 | if status == "open" and path.exists(photo_path):
226 | message += (
227 | "--"
228 | + boundary2
229 | + "\r\n"
230 | + "Content-Type: image/jpeg;"
231 | + "\r\n"
232 | + " name=contest{}.jpg\r\n".format(str(i))
233 | + "Content-Transfer-Encoding: base64\r\n"
234 | + "Content-Disposition: attachment;\r\n"
235 | + " filename=contest{}.jpg\r\n\r\n".format(str(i))
236 | + convert_to_baseline(photo_path)
237 | + "\r\n\r\n"
238 | )
239 |
240 | message += "--" + boundary2 + "--\r\n\r\n"
241 |
242 | data = {
243 | "username": "CMOC Bot",
244 | "content": contest_status,
245 | "avatar_url": "http://rc24.xyz/images/logo-small.png",
246 | "attachments": [
247 | {
248 | "fallback": contest_status,
249 | "color": "#CB7931",
250 | "author_name": "RiiConnect24 CMOC Script",
251 | "author_icon": "https://miicontest.wii.rc24.xyz/apple-touch-icon.png",
252 | "text": contest_status,
253 | "title": "Update!",
254 | "fields": [
255 | {
256 | "title": "Script",
257 | "value": "Check Mii Out Channel",
258 | "short": "false",
259 | }
260 | ],
261 | "thumb_url": "https://miicontest.wii.rc24.xyz/photo/{}.jpg".format(
262 | contest_id
263 | ),
264 | "footer": "RiiConnect24 Script",
265 | "footer_icon": "https://rc24.xyz/images/logo-small.png",
266 | "ts": int(calendar.timegm(datetime.utcnow().timetuple())),
267 | }
268 | ],
269 | }
270 |
271 | for url in config["webhook_urls"]:
272 | post_webhook = post(url, json=data, allow_redirects=True)
273 |
274 | cursor.execute("UPDATE contests SET sent = 1 WHERE id = %s", [contest_id])
275 |
276 | db.commit()
277 |
278 | message += boundary + "--\r\n"
279 |
280 | path = config["miicontest_path"]
281 |
282 | task_file = "{}/150/con_task1".format(path)
283 |
284 | print(message)
285 |
286 | with open(task_file + ".txt", "w") as f:
287 | f.write(message)
288 | f.close()
289 |
290 | system(
291 | "python3 ./sign_encrypt.py -t enc -in '{}.txt' -out '{}.bin' -key BE3715C308F341A8F16F0EF4FB1497AF -rsa /var/rc24/key/cmoc.pem".format(
292 | task_file, task_file
293 | )
294 | )
295 |
296 | remove(task_file + ".txt")
297 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/contestCLI.py:
--------------------------------------------------------------------------------
1 | from time import mktime
2 | from datetime import date, datetime
3 | import MySQLdb
4 | from json import load
5 |
6 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
7 | config = load(f)
8 |
9 | db = MySQLdb.connect(
10 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
11 | )
12 | cursor = db.cursor()
13 |
14 |
15 | class colors:
16 | HEADER = "\033[95m"
17 | BLUE = "\033[94m"
18 | GREEN = "\033[92m"
19 | WARNING = "\033[93m"
20 | FAIL = "\033[91m"
21 | END = "\033[0m"
22 | BOLD = "\033[1m"
23 | UNDERLINE = "\033[4m"
24 |
25 |
26 | def searchContest():
27 | search = str(
28 | input(
29 | "-> Please enter a keyword or phrase in the contest's topic or description: "
30 | )
31 | )
32 | formattedSearch = "%" + search.replace(" ", "%") + "%"
33 | cursor.execute(
34 | "SELECT id, description FROM contests WHERE topic LIKE %s OR description LIKE %s",
35 | (formattedSearch, formattedSearch),
36 | )
37 | result = cursor.fetchall()
38 |
39 | print(len(result), 'results for "' + str(search) + '"')
40 | print("ID | Description\n-------------------")
41 | for i in result:
42 | print(str(i[0]) + " " * (4 - len(str(i[0]))), "|", i[1])
43 |
44 | print()
45 |
46 |
47 | def showInfo(id):
48 | cursor.execute(
49 | "SELECT start, end, status, entrycount, topic, description FROM contests WHERE id = %s",
50 | [id],
51 | )
52 | result = cursor.fetchone()
53 | if result == None:
54 | print(f"{colors.FAIL}ID:", id, f"does not exist!{colors.END}")
55 |
56 | else:
57 | print("ID:", id)
58 | print("Start:", datetime.fromtimestamp(result[0] + 946684800).date())
59 | print("End:", datetime.fromtimestamp(result[1] + 946684800).date())
60 | print("Status:", result[2].capitalize())
61 | print("Submissions:", result[3])
62 | print("Topic:", result[4])
63 | print("Description:", result[5])
64 | print()
65 |
66 |
67 | print(
68 | f"{colors.BOLD}CMOC Contest Editor\nRiiConnect24 - Created by Josh{colors.END}\n1 - Add\n2 - Edit\n3 - Remove\n4 - View"
69 | )
70 | entry = int(input("-> Selection: "))
71 |
72 | if entry == 1:
73 | print("\nCreating new contest ->")
74 | while True:
75 | month = input(
76 | "-> Enter starting month (leave blank to start when next contest changes status): "
77 | )
78 |
79 | if len(month) == 0:
80 | currentTime = int(mktime(datetime.utcnow().timetuple())) - 946684800
81 | cursor.execute(
82 | "SELECT end, status, description FROM contests WHERE end > %s AND status != 'closed' ORDER BY end ASC LIMIT 1",
83 | [currentTime],
84 | )
85 | nextContest = cursor.fetchone()
86 |
87 | if len(nextContest) == 0:
88 | print(
89 | "There are no future contests scheduled. Please try again and enter the date manually."
90 | )
91 | exit()
92 |
93 | start = int(nextContest[0]) # set start time to next contest's end time
94 | startDate = datetime.fromtimestamp(start + 946684800).date()
95 |
96 | end = int(
97 | start + (7 * 24 * 60 * 60)
98 | ) # change status every 7 days until closure
99 | endDate = datetime.fromtimestamp(end + 946684800).date()
100 |
101 | if nextContest[1] == "waiting":
102 | nextStatus = "open"
103 | elif nextContest[1] == "open":
104 | nextStatus = "judging"
105 | elif nextContest[1] == "judging":
106 | nextStatus = "results"
107 | elif nextContest[1] == "results":
108 | nextStatus = "closed"
109 |
110 | print(
111 | "\nThe contest '{}' will become status '{}' on {}.".format(
112 | nextContest[2], nextStatus, startDate
113 | )
114 | )
115 | choice = str(
116 | input(
117 | "-> This new contest will begin at this date. Is this correct? [Y/N]: "
118 | )
119 | )
120 | if choice.upper() != "Y":
121 | print(f"{colors.FAIL}Contest adding aborted.{colors.END}")
122 | exit()
123 |
124 | break
125 |
126 | else:
127 | try:
128 | month = int(month)
129 | except:
130 | print(
131 | f"{colors.FAIL}Invalid month!{colors.END} Make sure you are entering the month by its number."
132 | )
133 | continue
134 |
135 | if month < datetime.now().month and datetime.now().month != 12:
136 | print("That month has already passed.")
137 | exit()
138 |
139 | day = int(input("-> Enter starting day: "))
140 | if (
141 | (month != 1 and datetime.now().month == 12)
142 | and day < datetime.now().day
143 | and month < datetime.now().month
144 | ):
145 | print("That date has already passed.")
146 | exit()
147 |
148 | year = datetime.now().year
149 | if datetime.now().month == 12 and month == 1:
150 | year = datetime.now().year + 1
151 |
152 | try:
153 | d = date(year, month, day)
154 |
155 | except ValueError as e:
156 | print(e)
157 | exit()
158 |
159 | start = int(mktime(d.timetuple()) - 946684800)
160 | startDate = datetime.fromtimestamp(start + 946684800).date()
161 |
162 | end = int(start + (7 * 24 * 60 * 60))
163 | endDate = datetime.fromtimestamp(end + 946684800).date()
164 | break
165 |
166 | while True:
167 | topic = str(input("-> Enter topic: "))
168 | if len(topic) > 10:
169 | print(
170 | "The contest topic cannot be longer than 10 characters. That topic is",
171 | len(topic),
172 | "characters.",
173 | )
174 |
175 | else:
176 | break
177 |
178 | while True:
179 | description = str(input("-> Enter description: "))
180 | if len(description) > 64:
181 | print(
182 | "The contest description cannot be longer than 64 characters. That topic is",
183 | len(description),
184 | "characters.",
185 | )
186 |
187 | else:
188 | break
189 |
190 | print()
191 | print("Description:", description)
192 | print("Topic:", topic)
193 | print("Start date:", startDate)
194 | print("End date:", endDate)
195 | choice = str(input("-> Is this information correct? [Y/N]: "))
196 | if choice.upper() == "Y":
197 | cursor.execute(
198 | "INSERT INTO contests(start, end, status, entrycount, topic, description) VALUES (%s, %s, %s, %s, %s, %s)",
199 | (start, end, "waiting", 0, topic, description),
200 | )
201 | db.commit()
202 | print(f"{colors.GREEN}Contest added successfully.{colors.END}")
203 |
204 | else:
205 | print(f"{colors.FAIL}Contest adding aborted.{colors.END}")
206 | exit()
207 |
208 | elif entry == 2:
209 | stop = False
210 | print("\nEditing contest ->")
211 |
212 | cursor.execute("SELECT id, description FROM contests ORDER BY id DESC limit 5")
213 | print("---5 most recently created contests---")
214 | print("ID | Description\n-------------------")
215 | for i in cursor.fetchall():
216 | print(str(i[0]) + " " * (4 - len(str(i[0]))), "|", i[1])
217 | print()
218 |
219 | while not stop:
220 | print("1 - Enter a contest ID\n2 - Search for a contest by keyword")
221 | selection = int(input("-> Selection: "))
222 | if selection == 1:
223 | id = int(input("-> Enter contest ID: "))
224 | cursor.execute("SELECT COUNT(*) FROM contests WHERE id = %s", [id])
225 | if cursor.fetchone()[0] == 0:
226 | print(f"{colors.FAIL}ID:", id, f"does not exist!{colors.END}\n")
227 | continue
228 |
229 | showInfo(id)
230 |
231 | print("1 - Change topic\n2 - Change description")
232 | editSelection = int(input("-> Selection: "))
233 |
234 | if editSelection == 1:
235 | while True:
236 | topic = str(input("-> Enter a new topic for the contest: "))
237 | if len(topic) > 10:
238 | print(
239 | "The contest topic cannot be longer than 10 characters. That topic is",
240 | len(topic),
241 | "characters.",
242 | )
243 | else:
244 | break
245 |
246 | cursor.execute(
247 | "UPDATE contests SET topic = %s WHERE id = %s", (topic, id)
248 | )
249 | db.commit()
250 | print(f"{colors.GREEN}Contest topic updated successfully.{colors.END}")
251 | stop = True
252 |
253 | elif editSelection == 2:
254 | while True:
255 | description = str(
256 | input("-> Enter a new description for the contest: ")
257 | )
258 | if len(description) > 64:
259 | print(
260 | "The contest topic cannot be longer than 64 characters. That topic is",
261 | len(description),
262 | "characters.",
263 | )
264 | else:
265 | break
266 |
267 | cursor.execute(
268 | "UPDATE contests SET description = %s WHERE id = %s",
269 | (description, id),
270 | )
271 | db.commit()
272 | print(
273 | f"{colors.GREEN}Contest description updated successfully.{colors.END}"
274 | )
275 | stop = True
276 |
277 | else:
278 | print(f"{colors.FAIL}Invalid entry!{colors.END}")
279 | stop = True
280 |
281 | elif selection == 2:
282 | searchContest()
283 |
284 | else:
285 | print(f"{colors.FAIL}Invalid entry!{colors.END}\n")
286 |
287 | elif entry == 3:
288 | stop = False
289 | print("\nRemoving contest ->")
290 |
291 | cursor.execute("SELECT id, description FROM contests ORDER BY id DESC limit 5")
292 | print("---5 most recently created contests---")
293 | print("ID | Description\n-------------------")
294 | for i in cursor.fetchall():
295 | print(str(i[0]) + " " * (4 - len(str(i[0]))), "|", i[1])
296 |
297 | while not stop:
298 | print("\n1 - Enter a contest ID\n2 - Search for a contest by keyword")
299 | selection = int(input("-> Selection: "))
300 |
301 | if selection == 1:
302 | id = int(input("-> Enter contest ID: "))
303 | cursor.execute(
304 | "SELECT status, entrycount FROM contests WHERE id = %s", [id]
305 | )
306 | result = cursor.fetchone()
307 | if result == None:
308 | print(f"ID:{colors.FAIL}", id, f"does not exist!{colors.END}")
309 | continue
310 |
311 | showInfo(id)
312 |
313 | if result[0] != "closed":
314 | print(
315 | "WARNING: This contest is still active with status '{}'.".format(
316 | result[0]
317 | )
318 | )
319 |
320 | if int(result[1]) != 0:
321 | print(
322 | "This contest and its",
323 | result[1],
324 | "miis will be permanently deleted.",
325 | )
326 |
327 | else:
328 | print("This contest will be permanently deleted.")
329 |
330 | choice = str(
331 | input(
332 | f"-> Are you sure you want to {colors.UNDERLINE}delete{colors.END} contest #{id}? [Y/N]: "
333 | )
334 | )
335 |
336 | if choice.upper() == "Y": # REMEMBER TO DELETE THE MIIS TOO IDIOT
337 | cursor.execute("DELETE FROM contests WHERE id = %s", [id])
338 | db.commit()
339 | print(f"{colors.GREEN}Contest", id, f"deleted.{colors.END}")
340 | stop = True
341 |
342 | else:
343 | print(f"{colors.FAIL}Contest deletion aborted.{colors.END}")
344 | stop = True
345 |
346 | elif selection == 2:
347 | searchContest()
348 |
349 | else:
350 | print(f"{colors.FAIL}Invalid entry!{colors.END}\n")
351 |
352 | elif entry == 4:
353 | print("\nViewing contest information ->")
354 | cursor.execute("SELECT id, description FROM contests ORDER BY id DESC limit 5")
355 | print("---5 most recently created contests---")
356 | print("ID | Description\n-------------------")
357 | for i in cursor.fetchall():
358 | print(str(i[0]) + " " * (4 - len(str(i[0]))), "|", i[1])
359 | print()
360 |
361 | while True:
362 | print("1 - Enter a contest ID\n2 - Search for a contest by keyword\n3 - Exit")
363 | selection = int(input("-> Selection: "))
364 |
365 | if selection == 1:
366 | id = int(input("-> Enter contest ID: "))
367 | showInfo(id)
368 |
369 | elif selection == 2:
370 | searchContest()
371 |
372 | elif selection == 3:
373 | break
374 |
375 | else:
376 | print(f"{colors.FAIL}Invalid entry!{colors.END}\n")
377 |
378 | db.close()
379 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/entrylist.py:
--------------------------------------------------------------------------------
1 | from cmoc import EntryList, Prepare
2 | import MySQLdb
3 | from json import load
4 |
5 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
6 | config = load(f)
7 |
8 | db = MySQLdb.connect(
9 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
10 | )
11 | cursor = db.cursor()
12 | el = EntryList()
13 | pr = Prepare()
14 |
15 | cursor.execute("SELECT id FROM contests WHERE status = 'judging'")
16 | ids = cursor.fetchall()
17 |
18 | for id in ids:
19 | id = id[0]
20 | cursor.execute(
21 | "SELECT craftsno, miidata FROM conmiis WHERE contest = %s ORDER BY RAND()", [id]
22 | )
23 | result = cursor.fetchall()
24 | miis = []
25 | for i in result:
26 | miis.append((i[0], i[1]))
27 |
28 | build = el.build(id, miis)
29 |
30 | for e in range(len(build)):
31 | with open(
32 | "{}/contest/{}/entry_list{}.ces".format(
33 | config["miicontest_path"], id, e + 1
34 | ),
35 | "wb",
36 | ) as file:
37 | file.write(pr.prepare(build[e]))
38 |
39 | with open(
40 | "decfiles/contests/{}/entry_list{}.dec".format(id, e + 1), "wb+"
41 | ) as file:
42 | file.write(build[e])
43 |
44 | db.close()
45 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/htmlcontest.py:
--------------------------------------------------------------------------------
1 | import MySQLdb
2 | from json import load
3 | from time import mktime
4 | from datetime import date, datetime
5 |
6 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
7 | config = load(f)
8 | date = str(datetime.today().strftime("%B %d, %Y"))
9 |
10 | beginning = (
11 | '\n\n
\n \n \n \n \nCheck Mii Out Channel - Contest Info \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n\n Contest Info \nCheck Mii Out Channel - RiiConnect24 \nThese are the contests currently running on the Check Mii Out Channel.
\nBack to Homepage \n'
12 | + date
13 | + ' \nAll times are measured in UTC
\n\n'
14 | )
15 |
16 | db = MySQLdb.connect(
17 | "localhost",
18 | config["dbuser"],
19 | config["dbpass"],
20 | "rc24_cmoc",
21 | use_unicode=True,
22 | charset="utf8mb4",
23 | )
24 | cursor = db.cursor()
25 |
26 | cursor.execute(
27 | "SELECT start,end,status,description FROM contests WHERE status != 'closed' AND status != 'waiting' ORDER BY status ASC, end ASC"
28 | )
29 | list = cursor.fetchall()
30 |
31 | month = int(datetime.now().month)
32 | day = int(datetime.now().day)
33 |
34 | tables = (
35 | beginning
36 | + "\t\n\t\tStart \n\t\tNext Rotation \n\t\tStatus \n\t\tDescription \n"
37 | )
38 |
39 | for i in range(len(list)):
40 | start = datetime.fromtimestamp(int(list[i][0]) + 946684800)
41 | end = datetime.fromtimestamp(int(list[i][1]) + 946684800)
42 | startDate = start.date()
43 | endDate = end.date()
44 | startTime = str(start.time())[:-3]
45 | endTime = str(end.time())[:-3]
46 |
47 | tables += "\t \n"
48 | tables += "\t\t{} {} \n".format(startDate, startTime)
49 | tables += "\t\t{} {} \n".format(endDate, endTime)
50 | tables += "\t\t{} \n".format(list[i][2].capitalize())
51 | tables += "\t\t{} \n".format(list[i][3])
52 |
53 | tables += "\t \n"
54 | # tables += str('\n' + list[i][0] + '' + str(list[i][1]))
55 |
56 | tables += "\n
\n\n"
57 |
58 | with open("/var/www/rc24/wapp.wii.com/miicontest/public_html/contest.html", "w") as file:
59 | file.write(tables)
60 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/htmlpopular.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import MySQLdb
3 | from lz4.block import decompress
4 | from base64 import b64encode, b64decode
5 | from os.path import exists
6 | from subprocess import call
7 | from json import load
8 | from datetime import datetime
9 | from cmoc import wii2studio
10 | from random import shuffle
11 | import sentry_sdk
12 |
13 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
14 | config = load(f)
15 |
16 | sentry_sdk.init(config["sentry_url"])
17 |
18 |
19 | def decodeMii(data):
20 | return decompress(b64decode(data.encode()), uncompressed_size=76)
21 |
22 |
23 | def decToEntry(num): # takes decimal int, outputs 12 digit entry number string
24 | num ^= ((num << 0x1E) ^ (num << 0x12) ^ (num << 0x18)) & 0xFFFFFFFF
25 | num ^= (num & 0xF0F0F0F) << 4
26 | num ^= (num >> 0x1D) ^ (num >> 0x11) ^ (num >> 0x17) ^ 0x20070419
27 |
28 | crc = (num >> 8) ^ (num >> 24) ^ (num >> 16) ^ (num & 0xFF) ^ 0xFF
29 | if 232 < (0xD4A50FFF < num) + (crc & 0xFF):
30 | crc &= 0x7F
31 |
32 | crc &= 0xFF
33 | return str(int((format(crc, "08b") + format(num, "032b")), 2)).zfill(12)
34 |
35 |
36 | date = str(datetime.today().strftime("%B %d, %Y"))
37 | db = MySQLdb.connect(
38 | "localhost",
39 | config["dbuser"],
40 | config["dbpass"],
41 | "rc24_cmoc",
42 | use_unicode=True,
43 | charset="utf8mb4",
44 | )
45 | cursor = db.cursor()
46 |
47 | headers = ["Mii", "Entry Number", "Nickname", "Initials", "Likes", "Creator"]
48 | for h in range(len(headers)):
49 | headers[h] = "\t\t" + headers[h] + " \n"
50 | headers = "\t\n" + "".join(headers) + "\t \n"
51 |
52 | cursor.execute("SELECT COUNT(*) FROM mii WHERE likes > 0")
53 | count = int(cursor.fetchone()[0])
54 | print("Popular Count:", count)
55 |
56 | # popular is always sorted by volatile likes first, but we combine miis ordered by permlikes to fill in the rest to equal 100 total miis
57 | if count >= 1000:
58 | extraCount = 0
59 | count = 1000
60 |
61 | else:
62 | extraCount = 1000 - count
63 |
64 | cursor.execute(
65 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.miidata, mii.nickname, mii.craftsno, artisan.nickname, artisan.master FROM mii, artisan WHERE mii.craftsno = artisan.craftsno AND likes > 0 ORDER BY likes DESC LIMIT %s",
66 | [count],
67 | )
68 | popularMiis = cursor.fetchall()
69 |
70 | cursor.execute(
71 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.miidata, mii.nickname, mii.craftsno, artisan.nickname, artisan.master FROM mii, artisan WHERE mii.permlikes > 21 AND mii.craftsno=artisan.craftsno ORDER BY mii.permlikes DESC LIMIT %s",
72 | [extraCount],
73 | )
74 | extraMiis = cursor.fetchall()
75 |
76 | row = list(set(popularMiis + extraMiis))[:500]
77 | shuffle(row)
78 | row = tuple(row)
79 |
80 | table = (
81 | f'These are all of the popular Miis that currently appear on the Check Mii Out Channel. Only the top 100 popular Miis are shown. Click on a Mii to download it.
\nBack to Homepage \n{date} \n\n'
82 | + headers
83 | )
84 | for i in range(len(row)):
85 | artisan = row[i][6]
86 | entryno = row[i][0]
87 | initial = row[i][1]
88 | mii_filename = (
89 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/render/entry-{}.mii".format(
90 | entryno
91 | )
92 | )
93 | if not exists(mii_filename):
94 | with open(mii_filename, "wb") as f:
95 | miidata = decodeMii(row[i][3])[:-2]
96 | miidata = (
97 | miidata[:28] + b"\x00\x00\x00\x00" + miidata[32:]
98 | ) # remove mac address from mii data
99 | f.write(miidata)
100 |
101 | # if not exists(mii_filename + '.png'):
102 | # call(['mono', '/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/MiiRender.exe', mii_filename])
103 |
104 | if len(initial) == 1:
105 | initial += "."
106 | elif len(initial) == 2:
107 | initial = initial[0] + "." + initial[1] + "."
108 |
109 | if bool(row[i][7]):
110 | artisan += ' '
111 |
112 | longentry = decToEntry(entryno)
113 | longentry = longentry[:4] + "-" + longentry[4:8] + "-" + longentry[8:12]
114 |
115 | table += "\t\n"
116 | table += f'\t\t \n'
117 | table += f"\t\t{longentry} \n"
118 | table += f"\t\t{row[i][4]} \n"
119 | table += f"\t\t{initial} \n"
120 | table += f"\t\t{row[i][2]} \n"
121 | table += f'\t\t{artisan} \n'
122 |
123 | table += "\t \n"
124 |
125 | table += "
\n"
126 |
127 | with open(
128 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/tables/popular.html", "w"
129 | ) as file:
130 | file.write(table)
131 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/htmlrankings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import MySQLdb
3 | from lz4.block import decompress
4 | from base64 import b64encode, b64decode
5 | from os.path import exists
6 | from subprocess import call
7 | from json import load
8 | from datetime import datetime
9 | from cmoc import wii2studio
10 |
11 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
12 | config = load(f)
13 |
14 |
15 | def decodeMii(data):
16 | return decompress(b64decode(data.encode()), uncompressed_size=76)
17 |
18 |
19 | date = str(datetime.today().strftime("%B %d, %Y"))
20 | db = MySQLdb.connect(
21 | "localhost",
22 | config["dbuser"],
23 | config["dbpass"],
24 | "rc24_cmoc",
25 | use_unicode=True,
26 | charset="utf8mb4",
27 | )
28 | cursor = db.cursor()
29 |
30 | countries = {
31 | 1: "jp",
32 | 8: "ai",
33 | 9: "ag",
34 | 10: "ar",
35 | 11: "aw",
36 | 12: "bs",
37 | 13: "bb",
38 | 14: "bz",
39 | 15: "bo",
40 | 16: "br",
41 | 17: "vg",
42 | 18: "ca",
43 | 19: "ky",
44 | 20: "cl",
45 | 21: "co",
46 | 22: "cr",
47 | 23: "dm",
48 | 24: "do",
49 | 25: "ec",
50 | 26: "sv",
51 | 27: "gf",
52 | 28: "gd",
53 | 29: "gp",
54 | 30: "gt",
55 | 31: "gy",
56 | 32: "ht",
57 | 33: "hn",
58 | 34: "jm",
59 | 35: "mq",
60 | 36: "mx",
61 | 37: "ms",
62 | 38: "cw",
63 | 39: "ni",
64 | 40: "pa",
65 | 41: "py",
66 | 42: "pe",
67 | 43: "kn",
68 | 44: "lc",
69 | 45: "vc",
70 | 46: "sr",
71 | 47: "tt",
72 | 48: "tc",
73 | 49: "us",
74 | 50: "uy",
75 | 51: "vi",
76 | 52: "ve",
77 | 64: "al",
78 | 65: "au",
79 | 66: "at",
80 | 67: "be",
81 | 68: "ba",
82 | 69: "bw",
83 | 70: "bg",
84 | 71: "hr",
85 | 72: "cy",
86 | 73: "cz",
87 | 74: "dk",
88 | 75: "ee",
89 | 76: "fi",
90 | 77: "fr",
91 | 78: "de",
92 | 79: "gr",
93 | 80: "hu",
94 | 81: "is",
95 | 82: "ie",
96 | 83: "it",
97 | 84: "lv",
98 | 85: "ls",
99 | 86: "li",
100 | 87: "lt",
101 | 88: "lu",
102 | 89: "mk",
103 | 90: "mt",
104 | 91: "me",
105 | 92: "mz",
106 | 93: "na",
107 | 94: "nl",
108 | 95: "nz",
109 | 96: "no",
110 | 97: "pl",
111 | 98: "pt",
112 | 99: "ro",
113 | 100: "ru",
114 | 101: "rs",
115 | 102: "sk",
116 | 103: "si",
117 | 104: "za",
118 | 105: "es",
119 | 106: "sz",
120 | 107: "se",
121 | 108: "ch",
122 | 109: "tr",
123 | 110: "gb",
124 | 111: "zm",
125 | 112: "zw",
126 | 113: "az",
127 | 114: "mr",
128 | 115: "ml",
129 | 116: "ne",
130 | 117: "td",
131 | 118: "sd",
132 | 119: "er",
133 | 120: "dj",
134 | 121: "so",
135 | 128: "tw",
136 | 136: "kr",
137 | 144: "hk",
138 | 145: "mo",
139 | 152: "id",
140 | 153: "sg",
141 | 154: "th",
142 | 155: "ph",
143 | 156: "my",
144 | 160: "cn",
145 | 168: "ae",
146 | 169: "in",
147 | 170: "eg",
148 | 171: "om",
149 | 172: "qa",
150 | 173: "kw",
151 | 174: "sa",
152 | 175: "sy",
153 | 176: "bh",
154 | 177: "jo",
155 | }
156 |
157 | headers = ["Rank", "Mii", "Artisan", "Votes", "Posts"]
158 | for h in range(len(headers)):
159 | headers[h] = "\t\t" + headers[h] + " \n"
160 | headers = "\t\n" + "".join(headers) + " \n"
161 |
162 | cursor.execute(
163 | "SELECT artisan.craftsno, artisan.miidata, artisan.nickname, artisan.country, artisan.votes, (SELECT COUNT(*) FROM mii WHERE mii.craftsno = artisan.craftsno) FROM artisan WHERE votes >= 3 AND craftsno != 100000993 ORDER BY votes DESC LIMIT 300"
164 | )
165 | row = cursor.fetchall()
166 |
167 | table = (
168 | f'These are currently the top Mii Artisans of the Check Mii Out Channel, ranked. Only the top 300 Mii Artisans are shown. Click on a Mii to download it.
\nBack to Homepage \n{date} \n\n'
169 | + headers
170 | )
171 | for i in range(len(row)):
172 | master = ""
173 | nickname = row[i][2]
174 | craftsno = row[i][0]
175 | mii_filename = (
176 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/render/crafts-{}.mii".format(
177 | craftsno
178 | )
179 | )
180 | if not exists(mii_filename):
181 | with open(mii_filename, "wb") as f:
182 | miidata = decodeMii(row[i][1])[:-2]
183 | miidata = (
184 | miidata[:28] + b"\x00\x00\x00\x00" + miidata[32:]
185 | ) # remove mac address from mii data
186 | f.write(miidata)
187 | f.close()
188 |
189 | # if not exists(mii_filename + '.png'):
190 | # call(['mono', 'MiiRender.exe', mii_filename])
191 |
192 | if int(row[i][4]) >= 1000:
193 | master = ' '
194 |
195 | try:
196 | country = (
197 | ' '
198 | )
199 |
200 | except KeyError:
201 | country = ""
202 |
203 | table += "\t\n"
204 | table += f"\t\t{i + 1} \n"
205 | table += f'\t\t \n'
206 | table += f'\t\t{country + nickname} {master} \n'
207 | table += f"\t\t{row[i][4]} \n"
208 | table += f"\t\t{row[i][5]} \n"
209 | table += "\t \n"
210 |
211 | table += "
\n"
212 |
213 | with open(
214 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/tables/rankings.html", "w"
215 | ) as file:
216 | file.write(table)
217 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/htmltop50.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import MySQLdb
3 | from lz4.block import decompress
4 | from base64 import b64encode, b64decode
5 | from os.path import exists
6 | from subprocess import call
7 | from json import load
8 | from datetime import datetime
9 | from cmoc import wii2studio
10 |
11 |
12 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
13 | config = load(f)
14 |
15 |
16 | def decodeMii(data):
17 | return decompress(b64decode(data.encode()), uncompressed_size=76)
18 |
19 |
20 | def decToEntry(num): # takes decimal int, outputs 12 digit entry number string
21 | num ^= ((num << 0x1E) ^ (num << 0x12) ^ (num << 0x18)) & 0xFFFFFFFF
22 | num ^= (num & 0xF0F0F0F) << 4
23 | num ^= (num >> 0x1D) ^ (num >> 0x11) ^ (num >> 0x17) ^ 0x20070419
24 |
25 | crc = (num >> 8) ^ (num >> 24) ^ (num >> 16) ^ (num & 0xFF) ^ 0xFF
26 | if 232 < (0xD4A50FFF < num) + (crc & 0xFF):
27 | crc &= 0x7F
28 |
29 | crc &= 0xFF
30 | return str(int((format(crc, "08b") + format(num, "032b")), 2)).zfill(12)
31 |
32 |
33 | date = str(datetime.today().strftime("%B %d, %Y"))
34 | db = MySQLdb.connect(
35 | "localhost",
36 | config["dbuser"],
37 | config["dbpass"],
38 | "rc24_cmoc",
39 | use_unicode=True,
40 | charset="utf8mb4",
41 | )
42 | cursor = db.cursor()
43 |
44 | headers = ["Mii", "Entry Number", "Nickname", "Initials", "Likes", "Creator"]
45 | for h in range(len(headers)):
46 | headers[h] = "\t\t" + headers[h] + " \n"
47 | headers = "\t\n" + "".join(headers) + " \n"
48 |
49 | cursor.execute(
50 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.miidata, mii.nickname, mii.craftsno, artisan.nickname, artisan.master FROM mii, artisan WHERE mii.craftsno = artisan.craftsno ORDER BY permlikes DESC LIMIT 50"
51 | )
52 | row = cursor.fetchall()
53 |
54 | table = (
55 | f'These are currently the top rated Miis on the Check Mii Out Channel, ranked. Click on a Mii to download it.
\nBack to Homepage \n{date} \n\n'
56 | + headers
57 | )
58 | for i in range(len(row)):
59 | artisan = row[i][6]
60 | entryno = row[i][0]
61 | initial = row[i][1]
62 | mii_filename = (
63 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/render/entry-{}.mii".format(
64 | entryno
65 | )
66 | )
67 | if not exists(mii_filename):
68 | with open(mii_filename, "wb") as f:
69 | miidata = decodeMii(row[i][3])[:-2]
70 | miidata = (
71 | miidata[:28] + b"\x00\x00\x00\x00" + miidata[32:]
72 | ) # remove mac address from mii data
73 | f.write(miidata)
74 |
75 | # if not exists(mii_filename + '.png'):
76 | # call(['mono', 'MiiRender.exe', mii_filename])
77 |
78 | if len(initial) == 1:
79 | initial += "."
80 | elif len(initial) == 2:
81 | initial = initial[0] + "." + initial[1] + "."
82 |
83 | if bool(row[i][7]):
84 | artisan += ' '
85 |
86 | longentry = decToEntry(entryno)
87 | longentry = longentry[:4] + "-" + longentry[4:8] + "-" + longentry[8:12]
88 |
89 | table += "\t\n"
90 | table += f'\t\t \n'
91 | table += f"\t\t{longentry} \n"
92 | table += f"\t\t{row[i][4]} \n"
93 | table += f"\t\t{initial} \n"
94 | table += f"\t\t{row[i][2]} \n"
95 | table += f'\t\t{artisan} \n'
96 |
97 | table += "\t \n"
98 |
99 | table += "
\n"
100 |
101 | with open(
102 | "/var/www/rc24/wapp.wii.com/miicontest/public_html/tables/top50.html", "w"
103 | ) as file:
104 | file.write(table)
105 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/jobs.cron:
--------------------------------------------------------------------------------
1 | #generate top 50 miis once a week
2 | 0 0 */5 * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./top50.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/top50.log
3 |
4 | #generate top 50 web miis once a week
5 | 5 0 */5 * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./htmltop50.py > /dev/null 2>&1
6 |
7 | #generate popular list once a day
8 | 0 0 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./popular.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/popular.log
9 |
10 | #generate popular web list once a day
11 | 5 0 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./htmlpopular.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/popular2.log 2>&1
12 |
13 | #generate grab bag every 30 minutes
14 | */30 * * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./randlist.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/randlist.log
15 |
16 | #generate popcrafts every 2 hours
17 | 0 */2 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./popcrafts.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/popcrafts.log
18 |
19 | #generate artisan web list every 2 hours
20 | 5 */2 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./htmlrankings.py >/dev/null 2>&1
21 |
22 | #generate contest web list every 2 hours
23 | 5 */2 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./htmlcontest.py >/dev/null 2>&1
24 |
25 | #generate wii sports resort popular list every day
26 | 0 0 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./wiisports.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/wsr.log
27 |
28 | #generate coninfo and condetail every 2 hours
29 | 0 */2 * * * cd /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel && /usr/local/bin/python3.7 ./coninfo.py > /var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/logs/coninfo.log
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/miikaitai.py:
--------------------------------------------------------------------------------
1 | # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
2 |
3 | from pkg_resources import parse_version
4 | from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO
5 |
6 |
7 | if parse_version(ks_version) < parse_version("0.7"):
8 | raise Exception(
9 | "Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s"
10 | % (ks_version)
11 | )
12 |
13 |
14 | class Mii(KaitaiStruct):
15 | def __init__(self, _io, _parent=None, _root=None):
16 | self._io = _io
17 | self._parent = _parent
18 | self._root = _root if _root else self
19 | self._read()
20 |
21 | def _read(self):
22 | self.invalid = self._io.read_bits_int(1) != 0
23 | self.gender = self._io.read_bits_int(1) != 0
24 | self.birth_month = self._io.read_bits_int(4)
25 | self.birth_day = self._io.read_bits_int(5)
26 | self.favorite_color = self._io.read_bits_int(4)
27 | self.favorite = self._io.read_bits_int(1) != 0
28 | self._io.align_to_byte()
29 | self.mii_name = (self._io.read_bytes(20)).decode(u"utf-16be")
30 | self.body_height = self._io.read_u1()
31 | self.body_weight = self._io.read_u1()
32 | self.avatar_id = [None] * (4)
33 | for i in range(4):
34 | self.avatar_id[i] = self._io.read_u1()
35 |
36 | self.client_id = [None] * (4)
37 | for i in range(4):
38 | self.client_id[i] = self._io.read_u1()
39 |
40 | self.face_type = self._io.read_bits_int(3)
41 | self.face_color = self._io.read_bits_int(3)
42 | self.facial_feature = self._io.read_bits_int(4)
43 | self.unknown = self._io.read_bits_int(3)
44 | self.mingle = self._io.read_bits_int(1) != 0
45 | self.unknown_2 = self._io.read_bits_int(1) != 0
46 | self.downloaded = self._io.read_bits_int(1) != 0
47 | self.hair_type = self._io.read_bits_int(7)
48 | self.hair_color = self._io.read_bits_int(3)
49 | self.hair_flip = self._io.read_bits_int(1) != 0
50 | self.unknown_3 = self._io.read_bits_int(5)
51 | self.eyebrow_type = self._io.read_bits_int(5)
52 | self.unknown_4 = self._io.read_bits_int(1) != 0
53 | self.eyebrow_rotation = self._io.read_bits_int(4)
54 | self.unknown_5 = self._io.read_bits_int(6)
55 | self.eyebrow_color = self._io.read_bits_int(3)
56 | self.eyebrow_size = self._io.read_bits_int(4)
57 | self.eyebrow_vertical = self._io.read_bits_int(5)
58 | self.eyebrow_horizontal = self._io.read_bits_int(4)
59 | self.eye_type = self._io.read_bits_int(6)
60 | self.unknown_6 = self._io.read_bits_int(2)
61 | self.eye_rotation = self._io.read_bits_int(3)
62 | self.eye_vertical = self._io.read_bits_int(5)
63 | self.eye_color = self._io.read_bits_int(3)
64 | self.unknown_7 = self._io.read_bits_int(1) != 0
65 | self.eye_size = self._io.read_bits_int(3)
66 | self.eye_horizontal = self._io.read_bits_int(4)
67 | self.unknown_8 = self._io.read_bits_int(5)
68 | self.nose_type = self._io.read_bits_int(4)
69 | self.nose_size = self._io.read_bits_int(4)
70 | self.nose_vertical = self._io.read_bits_int(5)
71 | self.unknown_9 = self._io.read_bits_int(3)
72 | self.mouth_type = self._io.read_bits_int(5)
73 | self.mouth_color = self._io.read_bits_int(2)
74 | self.mouth_size = self._io.read_bits_int(4)
75 | self.mouth_vertical = self._io.read_bits_int(5)
76 | self.glasses_type = self._io.read_bits_int(4)
77 | self.glasses_color = self._io.read_bits_int(3)
78 | self.unknown_10 = self._io.read_bits_int(1) != 0
79 | self.glasses_size = self._io.read_bits_int(3)
80 | self.glasses_vertical = self._io.read_bits_int(5)
81 | self.facial_hair_mustache = self._io.read_bits_int(2)
82 | self.facial_hair_beard = self._io.read_bits_int(2)
83 | self.facial_hair_color = self._io.read_bits_int(3)
84 | self.facial_hair_size = self._io.read_bits_int(4)
85 | self.facial_hair_vertical = self._io.read_bits_int(5)
86 | self.mole_enable = self._io.read_bits_int(1) != 0
87 | self.mole_size = self._io.read_bits_int(4)
88 | self.mole_vertical = self._io.read_bits_int(5)
89 | self.mole_horizontal = self._io.read_bits_int(5)
90 | self.unknown_11 = self._io.read_bits_int(1) != 0
91 | self._io.align_to_byte()
92 | self.creator_name = (self._io.read_bytes(20)).decode(u"utf-16be")
93 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/numberinfo.py:
--------------------------------------------------------------------------------
1 | from cmoc import QuickList, Prepare, ResetList
2 | import MySQLdb
3 | from json import load
4 | from time import sleep
5 |
6 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
7 | config = load(f)
8 | # get the top 50 most popular miis sorted by their permanent likes and add them to pop_list
9 |
10 |
11 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "rc24_cmoc")
12 | cursor = db.cursor()
13 |
14 | cursor.execute('SELECT COUNT(*) FROM mii')
15 | mii_count = int(cursor.fetchone()[0])
16 |
17 | cursor.execute('SELECT COUNT(*) FROM artisan')
18 | artisan_count = int(cursor.fetchone()[0])
19 |
20 | ql = QuickList()
21 | pr = Prepare()
22 |
23 | data = ql.numberinfoBuild(mii_count, artisan_count)
24 |
25 | with open(
26 | "{}/150/number_info.ces".format(config["miicontest_path"]), "wb"
27 | ) as file:
28 | file.write(pr.prepare(data))
29 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/popcrafts.py:
--------------------------------------------------------------------------------
1 | from cmoc import QuickList, Prepare
2 | from datetime import timezone, datetime
3 | import MySQLdb
4 | from json import load
5 |
6 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
7 | config = load(f)
8 |
9 | # get the top 99 artisans and adds them to popcrafts_list
10 |
11 | ql = QuickList()
12 | pr = Prepare()
13 |
14 | db = MySQLdb.connect(
15 | "localhost", config["dbuser"], config["dbpass"], "rc24_cmoc", charset="utf8mb4"
16 | )
17 | cursor = db.cursor()
18 |
19 | # 100000993 is the RC24 gold pants artisan
20 | cursor.execute(
21 | "SELECT craftsno FROM artisan WHERE craftsno !=100000993 ORDER BY votes DESC LIMIT 100"
22 | )
23 | count = cursor.fetchall()
24 |
25 | artisanlist = []
26 |
27 | for i in range(len(count)): # add the artisan data to each mii based on their craftsno
28 | cursor.execute(
29 | "SELECT master,lastpost FROM artisan WHERE craftsno = %s", [count[i][0]]
30 | )
31 | result = cursor.fetchone()
32 |
33 | lastpost = int(
34 | (
35 | datetime.now().replace(tzinfo=timezone.utc).timestamp()
36 | - result[1].replace(tzinfo=timezone.utc).timestamp()
37 | )
38 | / 60
39 | / 60
40 | ) # hours since artisan last posted
41 | master = int(result[0])
42 |
43 | if lastpost < 24: # if a new mii was uploaded within 24 hours, add new mii flag
44 | if master == 1:
45 | master = 3 # master artisan with new mii flag
46 |
47 | else:
48 | master = 2 # new mii flag
49 |
50 | cursor.execute(
51 | "SELECT craftsno,miidata,popularity,country FROM artisan WHERE craftsno = %s",
52 | [count[i][0]],
53 | )
54 | artisanData = cursor.fetchone()
55 | artisanData = (
56 | artisanData[:2] + (master,) + artisanData[2:]
57 | ) # insert master into the tuple where it would normally be retrieved from the db lmao
58 | artisanlist.append(artisanData)
59 |
60 | data = ql.popcraftsBuild(artisanlist)
61 | with open("{}/150/popcrafts_list.ces".format(config["miicontest_path"]), "wb") as file:
62 | file.write(pr.prepare(data))
63 |
64 | with open("decfiles/popcrafts_list.dec", "wb") as file:
65 | file.write(data)
66 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/popular.py:
--------------------------------------------------------------------------------
1 | from cmoc import QuickList, Prepare
2 | import MySQLdb
3 | from json import load
4 | from time import sleep
5 | from random import shuffle
6 |
7 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
8 | config = load(f)
9 |
10 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "rc24_cmoc")
11 | cursor = db.cursor()
12 |
13 | cursor.execute("SELECT COUNT(*) FROM mii WHERE likes > 0")
14 | count = int(cursor.fetchone()[0])
15 | print("Popular Count:", count)
16 |
17 | # popular is always sorted by volatile likes first, but we combine miis ordered by permlikes to fill in the rest to equal 100 total miis
18 | if count >= 1000:
19 | extraCount = 0
20 | count = 1000
21 |
22 | else:
23 | extraCount = 1000 - count
24 |
25 | cursor.execute(
26 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.skill, mii.country, mii.miidata, artisan.miidata, artisan.craftsno, artisan.master FROM mii, artisan WHERE mii.craftsno=artisan.craftsno ORDER BY mii.likes DESC LIMIT %s",
27 | [count],
28 | )
29 | popularMiis = cursor.fetchall()
30 |
31 | cursor.execute(
32 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.skill, mii.country, mii.miidata, artisan.miidata, artisan.craftsno, artisan.master FROM mii, artisan WHERE mii.permlikes > 21 AND mii.craftsno=artisan.craftsno ORDER BY mii.entryno DESC LIMIT %s",
33 | [extraCount],
34 | )
35 | extraMiis = cursor.fetchall()
36 |
37 | cursor.execute(
38 | "UPDATE mii SET likes = 0"
39 | ) # reset everyone's likes, but not their permlikes
40 |
41 | db.commit()
42 | db.close()
43 |
44 | res = []
45 | combined = list(set(popularMiis + extraMiis))[:500]
46 | shuffle(combined)
47 | combined = tuple(combined)
48 |
49 | for country in [0, 150]:
50 | # gets the most popular miis ordered by their volatile likes which resets to 0 when spot_list resets
51 | ql = QuickList()
52 | pr = Prepare()
53 |
54 | data = ql.build("SL", combined, country)
55 |
56 | with open(
57 | "{}/{}/spot_list.ces".format(config["miicontest_path"], country), "wb"
58 | ) as file:
59 | file.write(pr.prepare(data))
60 |
61 | with open("decfiles/spot_list.dec", "wb") as file:
62 | file.write(data)
63 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/randlist.py:
--------------------------------------------------------------------------------
1 | from cmoc import NumberedList, Prepare
2 | import MySQLdb
3 | from json import load
4 |
5 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
6 | config = load(f)
7 |
8 | ql = NumberedList()
9 | pr = Prepare()
10 |
11 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "rc24_cmoc")
12 | cursor = db.cursor()
13 |
14 | # grab bag is extremely unpredictable and can cause server spam or crash wiis if done incorrectly
15 | # the code below is basically all just trial and error
16 |
17 | a = [150, 150, 150, 0, 0, 0, 0, 0, 0, 0]
18 |
19 | bl = 0
20 | for n in a:
21 | bl += 1
22 |
23 | cursor.execute(
24 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.skill, mii.country, mii.miidata, artisan.miidata, artisan.craftsno, artisan.master FROM mii, artisan WHERE mii.craftsno = artisan.craftsno AND mii.permlikes<25 ORDER BY RAND() LIMIT %s",
25 | [n],
26 | ) # idk how this works it needs to be figured out
27 | miilist = cursor.fetchall()
28 |
29 | list_type = "RL" + str(bl)
30 |
31 | for country in [0, 150]:
32 | ql = NumberedList()
33 |
34 | data = ql.build(list_type, miilist, country)
35 |
36 | with open(
37 | "{}/{}/bargain_list{}.ces".format(
38 | config["miicontest_path"], country, str(bl).zfill(2)
39 | ),
40 | "wb",
41 | ) as file:
42 | file.write(pr.prepare(data))
43 |
44 | with open(
45 | "decfiles/bargain_list/bargain_list{}.dec".format(str(bl).zfill(2)), "wb"
46 | ) as file:
47 | file.write(data)
48 |
49 | a = [150, 150, 150, 0, 0, 0, 0, 0, 0, 0]
50 | nl = 0
51 | for t in a:
52 | nl += 1
53 |
54 | cursor.execute(
55 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.skill, mii.country, mii.miidata, artisan.miidata, artisan.craftsno, artisan.master FROM mii, artisan WHERE mii.craftsno = artisan.craftsno ORDER BY entryno DESC LIMIT %s",
56 | [t],
57 | )
58 | miilist = cursor.fetchall()
59 |
60 | list_type = "NL" + str(nl)
61 |
62 | for country in [0, 150]:
63 | ql = NumberedList()
64 |
65 | data = ql.build(list_type, miilist, country)
66 |
67 | with open(
68 | "{}/{}/new_list{}.ces".format(
69 | config["miicontest_path"], country, str(nl).zfill(2)
70 | ),
71 | "wb",
72 | ) as file:
73 | file.write(pr.prepare(data))
74 |
75 | with open(
76 | "decfiles/bargain_list/new_list{}.dec".format(str(nl).zfill(2)), "wb"
77 | ) as file:
78 | file.write(data)
79 |
80 | db.close()
81 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/sign_encrypt.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import binascii
3 | import collections
4 | import os
5 | import pyaes
6 | import rsa
7 | import struct
8 | import subprocess
9 |
10 |
11 | def u8(data):
12 | return struct.pack(">B", data)
13 |
14 |
15 | def u16(data):
16 | return struct.pack(">H", data)
17 |
18 |
19 | def u32(data):
20 | return struct.pack(">I", data)
21 |
22 |
23 | parser = argparse.ArgumentParser(description="Sign / Encrypt WiiConnect24 files.")
24 | parser.add_argument(
25 | "-t",
26 | "--type",
27 | type=str,
28 | nargs="+",
29 | help="Type of file. Set either enc for encrypted file, or dec for decrypted (compressed) file.",
30 | )
31 | parser.add_argument("-in", "--input", type=str, nargs="+", help="Input file.")
32 | parser.add_argument("-out", "--output", type=str, nargs="+", help="Output file.")
33 | parser.add_argument(
34 | "-c",
35 | "--compress",
36 | type=str,
37 | nargs="+",
38 | help="If set, this will compress the file before signing.",
39 | )
40 | parser.add_argument(
41 | "-key", "--aes-key", type=str, nargs="+", help="AES key in hex or a path."
42 | )
43 | parser.add_argument(
44 | "-iv", "--iv-key", type=str, nargs="+", help="AES IV in hex or a path."
45 | )
46 | parser.add_argument(
47 | "-rsa",
48 | "--rsa-key-path",
49 | type=str,
50 | nargs="+",
51 | help="RSA private key path. If not specified, it will use the private key in Private.pem if it exists.",
52 | )
53 |
54 | args = parser.parse_args()
55 |
56 | if args.compress is not None:
57 | subprocess.call(["cp", args.input[0], "temp"])
58 | subprocess.call(["lzss", "-evf", "temp"])
59 |
60 | if args.type[0] == "enc":
61 | filename = args.input[0]
62 | elif args.type[0] == "dec":
63 | filename = "temp"
64 |
65 | with open(filename, "rb") as f:
66 | data = f.read()
67 |
68 | """RSA sign the file."""
69 |
70 | if args.rsa_key_path is not None:
71 | rsa_key_path = args.rsa_key_path[0]
72 | else:
73 | rsa_key_path = "Private.pem"
74 |
75 | with open(rsa_key_path, "rb") as source_file:
76 | private_key_data = source_file.read()
77 |
78 | private_key = rsa.PrivateKey.load_pkcs1(private_key_data, "PEM")
79 |
80 | signature = rsa.sign(data, private_key, "SHA-1")
81 |
82 | if args.type[0] == "enc":
83 | if args.iv_key is not None:
84 | try:
85 | iv = binascii.unhexlify(args.iv_key[0])
86 | except:
87 | iv = open(args.iv_key[0], "rb").read()
88 | else:
89 | iv = os.urandom(16)
90 |
91 | try:
92 | key = binascii.unhexlify(args.aes_key[0])
93 | except:
94 | key = open(args.aes_key[0], "rb").read()
95 |
96 | aes = pyaes.AESModeOfOperationOFB(key, iv=iv)
97 | processed = aes.encrypt(data)
98 | elif args.type[0] == "dec":
99 | processed = data
100 |
101 | content = collections.OrderedDict()
102 |
103 | content["magic"] = b"WC24" if args.type[0] == "enc" else u32(0)
104 | content["version"] = u32(1) if args.type[0] == "enc" else u32(0)
105 | content["filler"] = u32(0)
106 | content["crypt_type"] = u8(1) if args.type[0] == "enc" else u8(0)
107 | content["pad"] = u8(0) * 3
108 | content["reserved"] = u8(0) * 32
109 | content["iv"] = iv if args.type[0] == "enc" else u8(0) * 16
110 | content["signature"] = signature
111 | content["data"] = processed
112 |
113 | if os.path.exists(args.output[0]):
114 | os.remove(args.output[0])
115 |
116 | if args.type[0] == "dec":
117 | os.remove("temp")
118 |
119 | for values in content.values():
120 | with open(args.output[0], "ab+") as f:
121 | f.write(values)
122 |
123 | print("Completed Successfully")
124 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/top50.py:
--------------------------------------------------------------------------------
1 | from cmoc import QuickList, Prepare, ResetList
2 | import MySQLdb
3 | from json import load
4 | from time import sleep
5 |
6 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
7 | config = load(f)
8 | # get the top 50 most popular miis sorted by their permanent likes and add them to pop_list
9 |
10 |
11 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "rc24_cmoc")
12 | cursor = db.cursor()
13 |
14 | cursor.execute(
15 | "SELECT mii.entryno, mii.initial, mii.permlikes, mii.skill, mii.country, mii.miidata, artisan.miidata, artisan.craftsno, artisan.master FROM mii, artisan WHERE mii.craftsno=artisan.craftsno ORDER BY permlikes DESC LIMIT 50"
16 | )
17 | miilist = cursor.fetchall()
18 |
19 | for country in [0, 150]:
20 | ql = QuickList()
21 | pr = Prepare()
22 |
23 | data = ql.build("PL", miilist, country)
24 |
25 | with open(
26 | "{}/{}/pop_list.ces".format(config["miicontest_path"], country), "wb"
27 | ) as file:
28 | file.write(pr.prepare(data))
29 |
--------------------------------------------------------------------------------
/Channels/Check_Mii_Out_Channel/wiisports.py:
--------------------------------------------------------------------------------
1 | import MySQLdb
2 | from json import load
3 | from cmoc import WSR
4 | from os import system
5 | from base64 import b64decode, b64encode
6 | from crc16 import crc16xmodem as crc
7 |
8 | with open("/var/rc24/File-Maker/Channels/Check_Mii_Out_Channel/config.json", "r") as f:
9 | config = load(f)
10 |
11 | db = MySQLdb.connect("localhost", config["dbuser"], config["dbpass"], "rc24_cmoc")
12 | cursor = db.cursor()
13 |
14 | cursor.execute("SELECT COUNT(*) FROM mii WHERE likes > 0 LIMIT 100")
15 | count = int(cursor.fetchone()[0])
16 | print("Popular Count:", count)
17 |
18 | # popular is always sorted by volatile likes first, but we combine miis ordered by permlikes to fill in the rest to equal 100 total miis
19 | if count >= 100:
20 | extraCount = 0
21 | count = 100
22 |
23 | else:
24 | extraCount = 100 - count
25 |
26 | cursor.execute(
27 | "SELECT mii.initial, mii.miidata, artisan.miidata, mii.entryno FROM mii, artisan WHERE mii.craftsno=artisan.craftsno ORDER BY mii.likes DESC LIMIT %s",
28 | [count],
29 | )
30 | popularMiis = cursor.fetchall()
31 |
32 | cursor.execute(
33 | "SELECT mii.initial, mii.miidata, artisan.miidata, mii.entryno FROM mii, artisan WHERE mii.permlikes < 25 AND mii.craftsno=artisan.craftsno ORDER BY mii.permlikes DESC LIMIT %s",
34 | [extraCount],
35 | )
36 | extraMiis = cursor.fetchall()
37 |
38 | db.close()
39 |
40 | ql = WSR().build(popularMiis + extraMiis)
41 | with open("decfiles/wiisports.dec", "wb") as file:
42 | file.write(ql)
43 |
44 | path = config["miicontest_path"]
45 |
46 | with open("{}/dd/wiisports.dec".format(path), "wb") as file:
47 | file.write(ql)
48 |
49 | # symlink all miidd country code files to wiisports.enc with its FULL directory path
50 | system(
51 | "python3 ./sign_encrypt.py -t enc -in '{}/dd/wiisports.dec' -out '{}/dd/wiisports.enc' -key 91D9A5DD10AAB467491A066EAD9FDD6F -rsa /var/rc24/key/miidd.pem".format(
52 | path, path
53 | )
54 | )
55 |
--------------------------------------------------------------------------------
/Channels/Everybody_Votes_Channel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Everybody_Votes_Channel/__init__.py
--------------------------------------------------------------------------------
/Channels/Everybody_Votes_Channel/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "key_path": "/path/to/key",
3 | "file_path": "/path/to/save/file",
4 | "production": true,
5 | "mysql_user": "user",
6 | "mysql_password": "password",
7 | "mysql_database": "database",
8 | "sentry_url": "http://status.domain.tld/",
9 | "webhook_urls": [
10 | "http://status.domain.tld/"
11 | ],
12 | "packVFF": true
13 | }
--------------------------------------------------------------------------------
/Channels/Everybody_Votes_Channel/jobs.cron:
--------------------------------------------------------------------------------
1 | 0 0 1,16 * 2,4,6 cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.Everybody_Votes_Channel.votes v >/dev/null 2>&1
2 | 0 0 * * 2,4,6 cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.Everybody_Votes_Channel.votes q n >/dev/null 2>&1
3 | 0 0 * * 2,4,6 cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.Everybody_Votes_Channel.votes r n >/dev/null 2>&1
4 | 0 0 1,16 * * cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.Everybody_Votes_Channel.votes q w >/dev/null 2>&1
5 | 0 0 1,16 * * cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.Everybody_Votes_Channel.votes r w >/dev/null 2>&1
6 | 5 0 1,16 * 2,4,6 cd /var/rc24/pack/ && /usr/local/bin/python3.7 votespack.py >/dev/null 2>&1
--------------------------------------------------------------------------------
/Channels/Everybody_Votes_Channel/voteslists.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import collections
5 |
6 | """List of countries the EVC uses."""
7 |
8 | countries = collections.OrderedDict()
9 |
10 | countries["Japan"] = ["日本", "Japan", "Japan", "Japon", "Japón", "Giappone", "Japan"]
11 | countries["Argentina"] = ["アルゼンチン", "Argentina", "Argentinien", "Argentine", "Argentina", "Argentina", "Argentinië"]
12 | countries["Brazil"] = ["ブラジル", "Brazil", "Brasilien", "Brésil", "Brasil", "Brasile", "Brazilië"]
13 | countries["Canada"] = ["カナダ", "Canada", "Kanada", "Canada", "Canadá", "Canada", "Canada"]
14 | countries["Chile"] = ["チリ", "Chile", "Chile", "Chili", "Chile", "Cile", "Chili"]
15 | countries["Colombia"] = ["コロンビア", "Colombia", "Kolumbien", "Colombie", "Colombia", "Colombia", "Colombia"]
16 | countries["Costa Rica"] = ["コスタリカ", "Costa Rica", "Costa Rica", "Costa Rica", "Costa Rica", "Costa Rica", "Costa Rica"]
17 | countries["Ecuador"] = ["エクアドル", "Ecuador", "Ecuador", "Equateur", "Ecuador", "Ecuador", "Ecuador"]
18 | countries["Guatemala"] = ["グアテマラ", "Guatemala", "Guatemala", "Guatemala", "Guatemala", "Guatemala", "Guatemala"]
19 | countries["Mexico"] = ["メキシコ", "Mexico", "Mexiko", "Mexique", "México", "Messico", "Mexico"]
20 | countries["Panama"] = ["パナマ", "Panama", "Panama", "Panama", "Panamá", "Panamá", "Panama"]
21 | countries["Peru"] = ["ペルー", "Peru", "Peru", "Pérou", "Perú", "Perù", "Peru"]
22 | countries["United States"] = ["アメリカ", "United States", "Vereinigte Staaten", "Etats-Unis d’Amérique", "Estados Unidos de América", "Stati Uniti d'America", "Verenigde Staten"]
23 | countries["Venezuela"] = ["ベネズエラ", "Venezuela", "Venezuela", "Venezuela", "Venezuela", "Venezuela", "Venezuela"]
24 | countries["Australia"] = ["オーストラリア", "Australia", "Australien", "Australie", "Australia", "Australia", "Australië"]
25 | countries["Austria"] = ["オーストリア", "Austria", "Österreich", "Autriche", "Austria", "Austria", "Oostenrijk"]
26 | countries["Belgium"] = ["ベルギー", "Belgium", "Belgien", "Belgique", "Bélgica", "Belgio", "België"]
27 | countries["Denmark"] = ["デンマーク", "Denmark", "Dänemark", "Danemark", "Dinamarca", "Danimarca", "Denemarken"]
28 | countries["Finland"] = ["フィンランド", "Finland", "Finnland", "Finlande", "Finlandia", "Finlandia", "Finland"]
29 | countries["France"] = ["フランス", "France", "Frankreich", "France", "Francia", "Francia", "Frankrijk"]
30 | countries["Germany"] = ["ドイツ", "Germany", "Deutschland", "Allemagne", "Alemania", "Germania", "Duitsland"]
31 | countries["Greece"] = ["ギリシャ", "Greece", "Griechenland", "Grèce", "Grecia", "Grecia", "Griekenland"]
32 | countries["Ireland"] = ["アイルランド", "Ireland", "Irland", "Irlande", "Irlanda", "Irlanda", "Ierland"]
33 | countries["Italy"] = ["イタリア", "Italy", "Italien", "Italie", "Italia", "Italia", "Italië"]
34 | countries["Luxembourg"] = ["ルクセンブルク", "Luxembourg", "Luxemburg", "Luxembourg", "Luxemburgo", "Lussemburgo", "Luxemburg"]
35 | countries["Netherlands"] = ["オランダ", "Netherlands", "Niederlande", "Pays-Bas", "Países Bajos", "Paesi Bassi", "Nederland"]
36 | countries["New Zealand"] = ["ニュージーランド", "New Zealand", "Neuseeland", "Nouvelle-Zélande", "Nueva Zelanda", "Nuova Zelanda", "Nieuw-Zeeland"]
37 | countries["Norway"] = ["ノルウェー", "Norway", "Norwegen", "Norvège", "Noruega", "Norvegia", "Noorwegen"]
38 | countries["Poland"] = ["ポーランド", "Poland", "Polen", "Pologne", "Polonia", "Polonia", "Polen"]
39 | countries["Portugal"] = ["ポルトガル", "Portugal", "Portugal", "Portugal", "Portugal", "Portogallo", "Portugal"]
40 | countries["Spain"] = ["スペイン", "Spain", "Spanien", "Espagne", "España", "Spagna", "Spanje"]
41 | countries["Sweden"] = ["スウェーデン", "Sweden", "Schweden", "Suède", "Suecia", "Svezia", "Zweden"]
42 | countries["Switzerland"] = ["スイス", "Switzerland", "Schweiz", "Suisse", "Suiza", "Svizzera", "Zwitserland"]
43 | countries["United Kingdom"] = ["イギリス", "United Kingdom", "Großbritannien", "Royaume-Uni", "Reino Unido", "Regno Unito", "Verenigd Koninkrijk"]
44 |
45 | """List of country codes."""
46 |
47 | country_codes = [1, 10, 16, 18, 20, 21, 22, 25, 30, 36, 40, 42, 49, 52, 65, 66, 67, 74, 76, 77, 78, 79, 82, 83, 88, 94, 95, 96, 97, 98, 105, 107, 108, 110]
48 |
49 | """These lists tell the script how many entries are used for the position tables."""
50 | """(if it's more than 1, that must mean the region is split up into multiple parts)"""
51 |
52 | position_table = collections.OrderedDict()
53 |
54 | position_table[1] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
55 | position_table[16] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]
56 | position_table[18] = [1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 1, 4, 3]
57 | position_table[21] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
58 | position_table[36] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
59 | position_table[40] = [2, 0, 1, 1, 1, 0, 0, 1, 1, 2]
60 | position_table[49] = [1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
61 | position_table[77] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
62 | position_table[78] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
63 | position_table[83] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
64 | position_table[94] = [1, 1, 1, 3, 1, 1, 1, 1, 1, 2, 1, 1]
65 | position_table[105] = [1, 1, 1, 1, 3, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
66 | position_table[110] = [1, 2, 2, 1, 1]
67 |
68 | """Data for the position table. Nintendo mixed these up to match the order votes were submitted in (I think)."""
69 | """That would be more effort to re-arrange the data in the position table, so I just made it read the values only if there is any votes for the region."""
70 |
71 | position_data = collections.OrderedDict()
72 |
73 | position_data[1] = "A2A4C828AF52B964B478AA64AA73AA87AD9BA5969B96A09EADA5A2A987947F8E78A096A5919B9B8782A591AF82AF7AB978AA6EAA6DB364AF73B96BC05AA546AA55AF4BB437B95FC358BA46C350C82DBE26C623CD2DD237C837D728E14849395A"
74 | position_data[16] = "A4862664E8648E1E4141C873D746CD9E7DA0B4467878B99B8746E35385BEC855C2AEE94D82DC4B6996C8A5AAE3699687E15AA064"
75 | position_data[18] = "87BE3CA009981EA064AAC8C3F0A8E1AAC89BD7C3D4BDAAAA50AF1E695C405649505A3C787841647D8E89"
76 | position_data[21] = "7C7D78739BC8695AAA5A71247D468D6B6E6E579887326946969BC896649B9119782D8C8C4BA58D4864B2677B647328194E19875A733E6E825A87"
77 | position_data[36] = "37508FB0786914465A5A69A54B7D98B69B9E8AAF9687E6A07DAF82918C787DA2649B91B476988BA1EBAA5F7D8CBE91A52B6F67B2A5C8C8C899AE738CC8B9D7B4"
78 | position_data[40] = "A05DAF7B1E7373737D5A739BAA5250823AA0"
79 | position_data[49] = "D25E78D252E748E1AA87917D3C7819645A64E04EDC5FC8A0BE872EE628DF18D98C5A3C46A064AA5F7869B46C9191E249DC64EB37A53FAF5087419169A08C5037D2737337735AE440DC55557D2D5AD746E254B95D7D7D2341CD55E84CC87D714BAA7878914164CD69DC3F272F9B46C3645550F0BE"
80 | position_data[77] = "8246DC465AB49196463CA06E28467864AA46E6E6C86E6E3296C87896C84678C88C14505A8C2D508CC8C8BE96"
81 | position_data[78] = "B95A64966EDC9BC8C86E5F417837AF2D7350467841AA3CBEBE919664781E8C8C"
82 | position_data[83] = "7D822328283C324B463264196432821E64466464786E82649682A08CA0A0BE96B9AABEBE96E63CB4"
83 | position_data[94] = "645AC8418C6496288214B40AAA82D223BE08A0C882B4B46E32C8788232C8"
84 | position_data[105] = "6E5F64E6A03C3C1EF852E65FCA739AD9A7E6B4E1C8E6EBE1641E7878503CC832AA73468C1E32A0968C28781E7832"
85 | position_data[110] = "B4B4738732E67846D71E82B4507D"
86 |
87 | """Number of regions for each country."""
88 |
89 | region_number = collections.OrderedDict()
90 |
91 | region_number[1] = 47
92 | region_number[10] = 24
93 | region_number[16] = 27
94 | region_number[18] = 13
95 | region_number[20] = 13
96 | region_number[21] = 33
97 | region_number[22] = 7
98 | region_number[25] = 22
99 | region_number[30] = 22
100 | region_number[36] = 32
101 | region_number[40] = 10
102 | region_number[42] = 25
103 | region_number[49] = 52
104 | region_number[52] = 25
105 | region_number[65] = 8
106 | region_number[66] = 9
107 | region_number[67] = 3
108 | region_number[74] = 17
109 | region_number[76] = 6
110 | region_number[77] = 26
111 | region_number[78] = 16
112 | region_number[79] = 13
113 | region_number[82] = 8
114 | region_number[83] = 20
115 | region_number[88] = 3
116 | region_number[94] = 12
117 | region_number[95] = 13
118 | region_number[96] = 5
119 | region_number[97] = 16
120 | region_number[98] = 7
121 | region_number[105] = 17
122 | region_number[107] = 21
123 | region_number[108] = 23
124 | region_number[110] = 5
125 |
126 | language_num = collections.OrderedDict()
127 |
128 | language_num[0] = "Japanese"
129 | language_num[1] = "English"
130 | language_num[2] = "German"
131 | language_num[3] = "French"
132 | language_num[4] = "Spanish"
133 | language_num[5] = "Italian"
134 | language_num[6] = "Dutch"
135 | language_num[7] = "Portuguese"
136 | language_num[8] = "French Canada"
137 |
138 | """Languages each country uses. The numbers correspond to the ones in the dictionary above."""
139 |
140 | country_language = collections.OrderedDict()
141 |
142 | country_language[1] = [1]
143 | country_language[10] = [1, 4, 8]
144 | country_language[16] = [1, 4, 7, 8]
145 | country_language[18] = [1, 4, 8]
146 | country_language[20] = [1, 4, 8]
147 | country_language[21] = [1, 4, 8]
148 | country_language[22] = [1, 4, 8]
149 | country_language[25] = [1, 4, 8]
150 | country_language[30] = [1, 4, 8]
151 | country_language[36] = [1, 4, 8]
152 | country_language[40] = [1, 4, 8]
153 | country_language[42] = [1, 4, 8]
154 | country_language[49] = [1, 4, 8]
155 | country_language[52] = [1, 4, 8]
156 | country_language[65] = [1]
157 | country_language[66] = [2, 3, 5, 6]
158 | country_language[67] = [2, 3, 5, 6]
159 | country_language[74] = [1]
160 | country_language[76] = [1]
161 | country_language[77] = [3]
162 | country_language[78] = [2]
163 | country_language[79] = [1, 4, 7]
164 | country_language[82] = [1]
165 | country_language[83] = [5]
166 | country_language[88] = [2, 3, 5, 6]
167 | country_language[94] = [6]
168 | country_language[95] = [1]
169 | country_language[96] = [1]
170 | country_language[97] = [1]
171 | country_language[98] = [1, 4, 7]
172 | country_language[105] = [4]
173 | country_language[107] = [1]
174 | country_language[108] = [2, 3, 5, 6]
175 | country_language[110] = [1]
176 |
177 | category_text = collections.OrderedDict()
178 |
179 | category_text[0] = "Thoughts"
180 | category_text[1] = "Personality"
181 | category_text[2] = "Surroundings"
182 | category_text[3] = "Experience"
183 | category_text[4] = "Knowledge"
184 |
185 | """Poll categories. The keys correspond to the ones above."""
186 |
187 | categories = collections.OrderedDict()
188 |
189 | categories[0] = 3
190 | categories[1] = 5
191 | categories[2] = 7
192 | categories[3] = 9
193 | categories[4] = 10
194 |
--------------------------------------------------------------------------------
/Channels/Forecast_Channel/README.md:
--------------------------------------------------------------------------------
1 | # Forecast Channel
2 |
3 | Thanks for your interest in helping out with RiiConnect24's File Maker.
4 |
5 | Please read on to learn how to add your own forecast city (and PR it to us).
6 |
7 | ## Files
8 |
9 | ### Scripts
10 |
11 | + forecast.py downloads the forecast from AccuWeather.
12 | + forecastlists.py has a list of the forecast cities that will be included.
13 | + forecastregions.py has region data for countries.
14 |
15 | ## How to Contribute
16 |
17 | If you would like to add your own forecast city, here are some instructions.
18 |
19 | We are not accepting new cities for these following countries:
20 |
21 | + Argentina
22 | + Australia
23 | + Brazil
24 | + Canada
25 | + Finland
26 | + France
27 | + Germany
28 | + Italy
29 | + Mexico
30 | + Portugal
31 | + United Kingdom
32 | + United States
33 |
34 | Your city needs to be popular enough in order for it to be added. If it has a decent population, that's good.
35 |
36 | To add a city, find the dictionary with the country code of the country you want to add a city for ([list of country codes are here](https://wiibrew.org/wiki/Country_Codes)) then make a dictionary entry. Make sure to fit it in the right place.
37 |
38 | Then comes the entries in this order:
39 |
40 | 1. A list of the city names in 7 languages in this order: (Japanese, English, German, French, Spanish, Italian, Dutch)
41 | 1. A list of the region names in 7 languages in the same order. You can find this in forecastregions.py or from another value.
42 | 1. A list of the country names in 7 languages in the same order. You can also find this in forecastregions.py or from another value.
43 | 1. Coordinates. You can get the latitude and longitude of the city, and convert it to what the Wii uses with this Python code:
44 |
45 | ```python
46 | def coord_decode(value):
47 | value = int(value,16)
48 | if value >= 0x8000: value -= 0x10000
49 | return value*0.0054931640625
50 | ```
51 |
52 | Then comes the first zoom factor. You can put it as any value from 0x0-0x9, as it's only something trivial for how the cities appear on the Globe.
53 | Put "030000" after that.
54 |
--------------------------------------------------------------------------------
/Channels/Forecast_Channel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Forecast_Channel/__init__.py
--------------------------------------------------------------------------------
/Channels/Forecast_Channel/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "key_path": "/path/to/key",
3 | "file_path": "/path/to/save/file",
4 | "production": true,
5 | "multithreaded": true,
6 | "wii_u_generation": true,
7 | "check_coordinates": false,
8 | "send_logs": true,
9 | "send_stats": true,
10 | "send_webhooks": true,
11 | "sentry_url": "http://example.com/",
12 | "webhook_urls": [
13 | "http://example.com/"
14 | ],
15 | "packVFF": true,
16 | "download_locations": false,
17 | "cloudflare_cache_purge": true,
18 | "cloudflare_zone_name": "id",
19 | "cloudflare_token": "token",
20 | "cloudflare_hostname": "http://example.com/"
21 | }
--------------------------------------------------------------------------------
/Channels/Forecast_Channel/jobs.cron:
--------------------------------------------------------------------------------
1 | 0 * * * * cd /var/rc24/File-Maker/ && /usr/bin/timeout 10m /usr/local/bin/python3.7 -m Channels.Forecast_Channel.forecast >/dev/null 2>&1
2 | 5 * * * * cd /var/rc24/pack/ && /usr/local/bin/python3.7 forecastpack.py >/dev/null 2>&1
--------------------------------------------------------------------------------
/Channels/News_Channel/README.md:
--------------------------------------------------------------------------------
1 | # News Channel
2 |
3 | These are the scripts we use to run the News Channel.
4 |
5 | ## Files
6 |
7 | ### Scripts
8 |
9 | + newsdownload.py downloads the news from the news site.
10 | + newsmake.py parses the news data into a data file the News Channel loads.
11 | + news.py calls newsdownload and newsmake with the news source to download from.
12 |
13 | The "logos" folder contains the news source logos that will show up on the News Channel.
14 |
15 | ## How to Add Your Own News Source
16 |
17 | If you want to add your own news source (if you have a valid reason to do so) here are some instructions.
18 |
19 | NOTE: I recommend you have a little bit of Python experience before you go any further.
20 |
21 | First, go in newsdownload.py and take a look at the `sources` dictionary:
22 |
23 | + Key: ID used for the news source.
24 | + Values:
25 | + name: Name of the news source. If using a custom logo for the news source, it needs to use the same name for the JPEG.
26 | + url: Base URL for the RSS feeds. We use `%s` substitutions for the categories (will be explained later)
27 | + lang: Language that the news is in. It's used for the newspaper module in order for it to know what language to get the news in. You can find the list of available languages with the `newspaper.languages()` command, but we only support 7 (Japanese, English, German, French, Spanish, Italian, and Dutch).
28 | + cat: Categories for the news:
29 | + Key: RSS feed name to substitute in url.
30 | + Value: ID used internally for the news category.
31 |
32 | Next, jump to the `Parse` class. In the `__init__` function, add an entry:
33 |
34 | + Key: Needs to match the key used for the `sources` dictionary (the news source ID).
35 | + Value: Name of the function we will define to manage special things for the news source.
36 |
37 | Now define the function for the `Parse` class. The two main things you usually have to manage in this function are:
38 |
39 | + Caption. newspaper unfortunately doesn't automatically parse captions, so normally you can grab it from the HTML with BeautifulSoup.
40 | + Location. Hopefully you are using a news source that has the location at the start of the article.
41 |
42 | Confused? Look at the other functions to see how it's done.
43 |
44 | Now for newsmake.py, take a look at the `sources` dictionary there:
45 |
46 | + Key: Needs to match the key used for the other `sources` dictionary (the news source ID).
47 | + Values:
48 | + topics_news: News topics used.
49 | + Key: Topic name used in the actual News Channel.
50 | + Value: Needs to match the value used in the `cat` dictionary inside the other `sources` dictionary.
51 | + languages: List of languages that is used for a hidden language selection screen that can only be toggled with an integer in the file. The way we do it is have it be [0] if the source is to be used in Japan, [1, 3, 4] for America, and [1, 2, 3, 4, 5, 6] for Europe.
52 | + language_code: Used to indicate the language used for the source.
53 | + 0: Japanese
54 | + 1: English
55 | + 2: German
56 | + 3: French
57 | + 4: Spanish
58 | + 5: Italian
59 | + 6: Dutch
60 | + country_code: Used to indicate the country code for the file. Since the news files we make are identical per country in a region, we just set it to 1 for Japan, 49 for America, and 110 for Europe.
61 |
62 | A couple more things, and you're done:
63 |
64 | In the `source_nums` dictionary in the `make_source_nums` function, you need to add an entry:
65 |
66 | + Key: Needs to match the key used for the `sources` dictionary from newsdownload.py (the news source ID).
67 | + Value: This needs to be an array:
68 | + 1st Entry: Logo to use.
69 | + 0: Custom Logo
70 | + 1: Mainichi
71 | + 2: News24
72 | + 3: Associated Press
73 | + 4: AFP
74 | + 5: ANP
75 | + 6: ANSA
76 | + 2nd Entry: Position for the logo to use.
77 | + 1: https://imgur.com/I3H59qp
78 | + 2: https://imgur.com/uzwwgsy
79 | + 3: https://imgur.com/heqzeTz
80 | + 4: https://imgur.com/AJAQndN
81 | + 5: https://imgur.com/3rQxpVJ
82 | + 6: https://imgur.com/f1O8VjS
83 |
84 | If using a custom logo, add the ID that matches the one used in `sources` from newsdownload.py to the `sources` array in the `make_source_pictures` function
85 |
86 | That's it! If you made it this far, I'd give you a few cookies if I could, unless you just skipped all the way to the end of here.
--------------------------------------------------------------------------------
/Channels/News_Channel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/__init__.py
--------------------------------------------------------------------------------
/Channels/News_Channel/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "key_path": "/path/to/key",
3 | "file_path": "/path/to/save/file",
4 | "force_all": false,
5 | "google_maps_api_key": "key",
6 | "production": true,
7 | "sentry_url": "http://status.domain.tld/",
8 | "webhook_urls": [
9 | "http://status.domain.tld/"
10 | ],
11 | "packVFF": true
12 | }
--------------------------------------------------------------------------------
/Channels/News_Channel/jobs.cron:
--------------------------------------------------------------------------------
1 | 0 * * * * cd /var/rc24/File-Maker/ && /usr/local/bin/python3.7 -m Channels.News_Channel.news >/dev/null 2>&1
2 | 5 * * * * cd /var/rc24/pack/ && /usr/local/bin/python3.7 newspack.py >/dev/null 2>&1
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/AFP_French.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/AFP_French.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/AFP_German.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/AFP_German.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/AFP_Spanish.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/AFP_Spanish.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/ANP.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/ANP.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/AP.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/AP.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/CanadianPress.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/CanadianPress.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/Reuters.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/Reuters.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/logos/SID.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/News_Channel/logos/SID.jpg
--------------------------------------------------------------------------------
/Channels/News_Channel/news.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | from Channels.News_Channel import newsdownload, newsmake
5 | from .newsdownload import News
6 | from .newsmake import NewsMake
7 | import sys
8 | import threading
9 | from utils import *
10 |
11 |
12 | def main():
13 | print("News Channel File Generator \nBy Larsen Vallecillo / www.rc24.xyz\n")
14 | if len(sys.argv) > 1:
15 | download(sys.argv[1])
16 | else:
17 | threads = []
18 |
19 | sources = [
20 | "ap_english",
21 | "ap_spanish",
22 | "reuters_europe_english",
23 | "afp_french",
24 | "afp_german",
25 | "afp_spanish",
26 | "ansa_italian",
27 | "anp_dutch",
28 | "reuters_japanese",
29 | "ap_canada",
30 | "ap_australia",
31 | ]
32 |
33 | for source in sources:
34 | t = threading.Thread(target=download, args=(source,))
35 | threads.append(t)
36 | t.start()
37 |
38 | for t in threads:
39 | t.join()
40 |
41 |
42 | def download(source):
43 | try:
44 | if source == "ap_english":
45 | NewsMake("AP English", "ap_english", 1, "America", News("ap_english"))
46 | elif source == "ap_spanish":
47 | NewsMake("AP Spanish", "ap_spanish", 4, "America", News("ap_spanish"))
48 | elif source == "reuters_europe_english":
49 | NewsMake(
50 | "Reuters Europe English",
51 | "reuters_europe_english",
52 | 1,
53 | "Europe",
54 | News("reuters_europe_english"),
55 | )
56 | elif source == "afp_french":
57 | NewsMake("AFP French", "afp_french", 3, "International", News("afp_french"))
58 | elif source == "afp_german":
59 | NewsMake("AFP German", "afp_german", 2, "Europe", News("afp_german"))
60 | elif source == "afp_spanish":
61 | NewsMake("AFP Spanish", "afp_spanish", 4, "Europe", News("afp_spanish"))
62 | elif source == "ansa_italian":
63 | NewsMake("ANSA Italian", "ansa_italian", 5, "Europe", News("ansa_italian"))
64 | elif source == "anp_dutch":
65 | NewsMake("ANP Dutch", "anp_dutch", 6, "Europe", News("anp_dutch"))
66 | elif source == "reuters_japanese":
67 | NewsMake(
68 | "Reuters Japanese",
69 | "reuters_japanese",
70 | 0,
71 | "Japan",
72 | News("reuters_japanese"),
73 | )
74 | elif source == "ap_canada":
75 | NewsMake("AP Canada", "ap_canada", 1, "Canada", News("ap_canada"))
76 | elif source == "ap_australia":
77 | NewsMake(
78 | "AP Australia", "ap_australia", 1, "Australia", News("ap_australia")
79 | )
80 | else:
81 | print("Invalid source specified.")
82 | exit()
83 | except Exception as e:
84 | print("Failed to make news for " + source + ".")
85 | raise e
86 |
87 |
88 | if __name__ == "__main__":
89 | main()
90 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/__init__.py
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "file_path": "/path/to/ninchannel/folder",
3 | "make_info": true,
4 | "production": false
5 | }
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/dstrial_header.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | import collections
3 | import struct
4 | import sys
5 |
6 |
7 | def u8(data):
8 | if not 0 <= data <= 255:
9 | log("u8 out of range: %s" % data, "INFO")
10 | data = 0
11 | return struct.pack(">B", data)
12 |
13 |
14 | def u16(data):
15 | if not 0 <= data <= 65535:
16 | log("u16 out of range: %s" % data, "INFO")
17 | data = 0
18 | return struct.pack(">H", data)
19 |
20 |
21 | def u32(data):
22 | if not 0 <= data <= 4294967295:
23 | log("u32 out of range: %s" % data, "INFO")
24 | data = 0
25 | return struct.pack(">I", data)
26 |
27 |
28 | def u32_littleendian(data):
29 | if not 0 <= data <= 4294967295:
30 | log("u32 little endian out of range: %s" % data, "INFO")
31 | data = 0
32 | return struct.pack(" ")
37 | sys.exit(1)
38 | elif len(sys.argv[2]) > 98:
39 | print("Error: Name must be less than or equal to 98 characters.")
40 | sys.exit(1)
41 | elif len(sys.argv[3]) != 4:
42 | print("Error: Title ID must be 4 characters.")
43 | sys.exit(1)
44 |
45 |
46 | class make_rom:
47 | def __init__(self):
48 | self.open_rom()
49 | self.make_rom()
50 | self.write_file()
51 |
52 | print("Completed Successfully")
53 |
54 | def open_rom(self):
55 | self.rom = open(sys.argv[1], "rb").read()
56 |
57 | def make_rom(self):
58 | self.header = collections.OrderedDict()
59 |
60 | self.header["unknown"] = u16(0)
61 | self.header["version"] = u8(6)
62 | self.header["unknown_region"] = u8(2)
63 | self.header["filesize"] = u32(332 + len(self.rom))
64 | self.header["crc32"] = u32(0)
65 | self.header["country_code"] = u32(49)
66 | self.header["language_code"] = u32(1)
67 | self.header["rom_offset"] = u32(332)
68 | self.header["rom_size"] = u32(len(self.rom))
69 | self.header["game_title"] = sys.argv[2].encode("utf-16be").ljust(98, b"\0")
70 | self.header["game_description"] = "Nintendo Channel Demo".encode(
71 | "utf-16be"
72 | ).ljust(194, b"\0")
73 | self.header["removal_year"] = u16(65535) # No removal year
74 | self.header["removal_month"] = u8(255) # No removal month
75 | self.header["removal_day"] = u8(255) # No removal day
76 | self.header["company_id"] = u32(1)
77 | self.header["title_id"] = sys.argv[3].encode()
78 |
79 | def write_file(self):
80 | self.writef = open(sys.argv[1] + "-output.bin", "wb")
81 |
82 | for values in self.header.values():
83 | self.writef.write(values)
84 |
85 | self.writef.write(self.rom)
86 |
87 | self.readf = open(sys.argv[1] + "-output.bin", "rb")
88 |
89 | self.writef.seek(8)
90 | self.writef.write(
91 | binascii.unhexlify(
92 | format(binascii.crc32(self.readf.read()) & 0xFFFFFFFF, "08x")
93 | )
94 | )
95 |
96 | self.writef.close()
97 |
98 |
99 | make_rom()
100 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/info.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | import glob
3 | import json
4 | import os
5 | import struct
6 | import sys
7 | import textwrap
8 | import requests
9 | from PIL import Image
10 | from ninfile2 import GameTDB
11 |
12 | with open("./config.json", "rb") as f:
13 | config = json.load(f)
14 |
15 |
16 | def u8(data):
17 | if not 0 <= data <= 255:
18 | print("u8 out of range: %s" % data, "INFO")
19 | data = 0
20 | return struct.pack(">B", data)
21 |
22 |
23 | def u16(data):
24 | if not 0 <= data <= 65535:
25 | print("u16 out of range: %s" % data, "INFO")
26 | data = 0
27 | return struct.pack(">H", data)
28 |
29 |
30 | def u32(data):
31 | if not 0 <= data <= 4294967295:
32 | print("u32 out of range: %s" % data, "INFO")
33 | data = 0
34 | return struct.pack(">I", data)
35 |
36 |
37 | def enc(text, length):
38 | if len(text) > length:
39 | print("Error: Text too long.")
40 | return text.encode("utf-16be").ljust(length, b"\0")[:length]
41 |
42 |
43 | if len(sys.argv) != 3:
44 | print("Usage: info.py ")
45 | sys.exit(1)
46 | elif len(sys.argv[2]) != 4 and len(sys.argv[2]) != 5:
47 | print("Error: Title ID must be 4 or 5 characters.")
48 | sys.exit(1)
49 |
50 |
51 | class MakeInfo:
52 | def __init__(self, databases):
53 | self.header = {}
54 | self.databases = databases
55 |
56 | self.make_header()
57 | self.write_gametdb_info()
58 | self.write_jpegs()
59 | self.write_file()
60 |
61 | print("Completed Successfully")
62 |
63 | def offset_count(self):
64 | """
65 | This function returns the offset of where the selected table is
66 | """
67 | return sum(len(values) for values in list(self.header.values()) if values)
68 |
69 | def make_header(self):
70 | self.header["unknown"] = u16(0)
71 | self.header["version"] = u8(6)
72 | self.header["unknown_region"] = u8(2)
73 | self.header["filesize"] = u32(0)
74 | self.header["crc32"] = u32(0)
75 | self.header["dllistid"] = u32(0)
76 | self.header["country_code"] = u32(49)
77 | self.header["language_code"] = u32(1)
78 | self.header["ratings_table_offset"] = u32(0)
79 | self.header["times_played_table_offset"] = u32(0)
80 | self.header["people_who_liked_this_also_liked_entry_number"] = u32(0)
81 | self.header["people_who_liked_this_also_liked_table_offset"] = u32(0)
82 | self.header["related_titles_entry_number"] = u32(0)
83 | self.header["related_titles_table_offset"] = u32(0)
84 | self.header["videos_entry_number"] = u32(0)
85 | self.header["videos_table_offset"] = u32(0)
86 | self.header["demos_entry_number"] = u32(0)
87 | self.header["demos_table_offset"] = u32(0)
88 | self.header["unknown_2"] = u32(0) * 2
89 | self.header["picture_offset"] = u32(0)
90 | self.header["picture_size"] = u32(0)
91 | self.header["unknown_3"] = u32(0)
92 | self.header["rating_picture_offset"] = u32(0)
93 | self.header["rating_picture_size"] = u32(0)
94 | for i in range(1, 8):
95 | self.header["rating_detail_picture_%s_offset" % i] = u32(0)
96 | self.header["rating_detail_picture_%s_size" % i] = u32(0)
97 | self.header["unknown_4"] = u32(0) * 2
98 | self.header["soft_id"] = u32(0)
99 | self.header["game_id"] = u32(0)
100 | self.header["platform_flag"] = u8(0)
101 | self.header["company_id"] = u32(2)
102 | self.header["unknown_5"] = u16(1)
103 | self.header["unknown_6"] = u16(1)
104 | self.header["unknown_7"] = u8(1)
105 | self.header["wii_shop_channel_button_flag"] = u8(1)
106 | self.header["purchase_button_flag"] = u8(0)
107 | self.header["release_year"] = u16(0)
108 | self.header["release_month"] = u8(0)
109 | self.header["release_day"] = u8(0)
110 | self.header["shop_points"] = u32(0)
111 | # Seems to have enabled
112 | self.header["unknown_8_0"] = u8(4)
113 | self.header["unknown_8_1"] = u8(1)
114 | self.header["unknown_8_2"] = u8(0)
115 | self.header["unknown_8_3"] = u8(4)
116 | self.header["wii_remote_flag"] = u8(0)
117 | self.header["nunchuk_flag"] = u8(0)
118 | self.header["classic_controller_flag"] = u8(0)
119 | self.header["gamecube_controller_flag"] = u8(0)
120 | self.header["mii_flag"] = u8(0)
121 | self.header["online_flag"] = u8(0)
122 | self.header["wiiconnect24_flag"] = u8(0)
123 | self.header["nintendo_wifi_connection_flag"] = u8(0)
124 | self.header["downloadable_content_flag"] = u8(0)
125 | self.header["wireless_play_flag"] = u8(0)
126 | self.header["download_play_flag"] = u8(0)
127 | self.header["touch_generations_flag"] = u8(0)
128 | self.header["language_chinese_flag"] = u8(0)
129 | self.header["language_korean_flag"] = u8(0)
130 | self.header["language_japanese_flag"] = u8(0)
131 | self.header["language_english_flag"] = u8(0)
132 | self.header["language_french_flag"] = u8(0)
133 | self.header["language_spanish_flag"] = u8(0)
134 | self.header["language_german_flag"] = u8(0)
135 | self.header["language_italian_flag"] = u8(0)
136 | self.header["language_dutch_flag"] = u8(0)
137 | for i in range(1, 11):
138 | self.header["unknown_9_%s" % i] = u8(0)
139 | self.header["title"] = b"\0" * 62
140 | self.header["subtitle"] = b"\0" * 62
141 | self.header["short_title"] = b"\0" * 62
142 | for i in range(1, 4):
143 | self.header["description_text_%s" % i] = b"\0" * 82
144 | self.header["genre_text"] = b"\0" * 58
145 | self.header["players_text"] = b"\0" * 82
146 | self.header["peripherals_text"] = b"\0" * 88
147 | self.header["unknown_10"] = b"\0" * 80
148 | self.header["disclaimer_text"] = b"\0" * 4800
149 | self.header["unknown_11"] = u8(9)
150 | self.header["distribution_date_text"] = b"\0" * 82
151 | self.header["wii_points_text"] = b"\0" * 82
152 | for i in range(1, 11):
153 | self.header["custom_field_text_%s" % i] = b"\0" * 82
154 |
155 | def write_gametdb_info(self):
156 | for s in self.databases[sys.argv[1]][1].findall("game"):
157 | if (
158 | sys.argv[1] == "Switch"
159 | and s.find("id").text == sys.argv[2]
160 | or s.find("id").text[:4] == sys.argv[2]
161 | and s.find("type") != "CUSTOM"
162 | ):
163 | print("Found {}!".format(sys.argv[2]))
164 |
165 | for c in self.databases["Wii"][1].findall("companies"):
166 | for i, company in enumerate(c.findall("company")):
167 | # Firstly, we will try to find the company by the company code.
168 | # This method will only work for disc games. As such, methods below exist
169 | if s.find("id").text[4:] != "":
170 | if s.find("id").text[4:] == company.get("code"):
171 | id = company.get("code").encode()
172 | id = int(id.hex(), base=16)
173 | self.header["company_id"] = u32(id)
174 | break
175 |
176 | # If None we will default to Nintendo as well
177 | if s.find("publisher").text is None:
178 | self.header["company_id"] = u32(0x3031)
179 | break
180 | if company.get("name") in s.find("publisher").text:
181 | id = company.get("code").encode()
182 | id = int(id.hex(), base=16)
183 | self.header["company_id"] = u32(id)
184 | break
185 |
186 | self.header["game_id"] = sys.argv[2].encode("utf-8")[:4]
187 |
188 | # Get the game type
189 | game_type = {
190 | "3DS": {
191 | None: 0x12,
192 | "3DS": 0x12,
193 | "3DSWare": 0x13,
194 | "New3DS": 0x18,
195 | "New3DSWare": 0x19,
196 | "VC-NES": 0x1A,
197 | "VC-GB": 0x1B,
198 | "VC-GBC": 0x1C,
199 | "VC-GBA": 0x1D,
200 | "VC-GG": 0x1E,
201 | },
202 | "NDS": {
203 | None: 0x0A,
204 | "DS": 0x0A,
205 | "DSi": 0x10,
206 | "DSiWare": 0x11,
207 | },
208 | "Switch": {
209 | None: 0x17,
210 | "Switch": 0x17,
211 | "eShop": 0x27,
212 | },
213 | "Wii": {
214 | None: 0x01,
215 | "Wii": 0x01,
216 | "Channel": 0x02,
217 | "WiiWare": 0x0B,
218 | "VC-NES": 0x03,
219 | "VC-SNES": 0x04,
220 | "VC-N64": 0x05,
221 | "VC-SMS": 0x0C,
222 | "VC-MD": 0x07,
223 | "VC-PCE": 0x06,
224 | "VC-NEOGEO": 0x08,
225 | "VC-Arcade": 0x0E,
226 | "VC-C64": 0x0D,
227 | "VC-MSX": 0x28,
228 | },
229 | "WiiU": {
230 | None: 0x15,
231 | "WiiU": 0x15,
232 | "eShop": 0x16,
233 | "VC-NES": 0x1F,
234 | "VC-SNES": 0x20,
235 | "VC-N64": 0x21,
236 | "VC-GBA": 0x22,
237 | "VC-DS": 0x23,
238 | "VC-PCE": 0x24,
239 | "VC-MSX": 0x25,
240 | "Channel": 0x26,
241 | }
242 | } # The XML returns None for disc games when we query the type.
243 | if s.find("type").text in game_type[sys.argv[1]]:
244 | if s.find("type").text == "WiiU" or s.find("type").text == "Switch":
245 | self.header["platform_flag"] = u8(0)
246 | else:
247 | self.header["platform_flag"] = u8(
248 | game_type[sys.argv[1]][s.find("type").text]
249 | )
250 | else:
251 | print("Could not find game type")
252 | sys.exit(1)
253 |
254 | self.header["purchase_button_flag"] = u8(
255 | 1
256 | ) # we'll make it go to gametdb
257 | # Some games, more notably DSiWare and some 3DS games do not have a release date in the xml.
258 | # Due to this, we must set defaults in the case of no date.
259 | if s.find("date").get("year") == "":
260 | release_year = 65535
261 | else:
262 | release_year = s.find("date").get("year")
263 | if s.find("date").get("month") == "":
264 | release_month = 255
265 | else:
266 | release_month = s.find("date").get("month")
267 | if s.find("date").get("day") == "":
268 | release_day = 255
269 | else:
270 | release_day = s.find("date").get("day")
271 |
272 | self.header["release_year"] = u16(int(release_year))
273 | self.header["release_month"] = u8(int(release_month) - 1)
274 | self.header["release_day"] = u8(int(release_day))
275 |
276 | controllers = {
277 | "wiimote": "wii_remote",
278 | "nunchuk": "nunchuk",
279 | "classiccontroller": "classic_controller",
280 | "gamecube": "gamecube_controller",
281 | "mii": "mii",
282 | }
283 | controllers2 = {
284 | "wheel": "Wii Wheel",
285 | "balanceboard": "Wii Balance Board",
286 | "wiispeak": "Wii Speak",
287 | "microphone": "Microphone",
288 | "guitar": "Guitar",
289 | "drums": "Drums",
290 | "dancepad": "Dance Pad",
291 | "keyboard": "Keyboard",
292 | "udraw": "uDraw",
293 | }
294 |
295 | other_peripherals = False
296 |
297 | for controller in s.find("input").findall("control"):
298 | if controller.get("type") in controllers:
299 | self.header[
300 | "{}_flag".format(controllers[controller.get("type")])
301 | ] = u8(1)
302 | elif controller.get("type") in controllers2:
303 | if not other_peripherals:
304 | self.header["peripherals_text"] = ""
305 |
306 | if (
307 | sys.argv[1] != "Wii"
308 | and controllers2[controller.get("type")] == "Wii Wheel"
309 | ):
310 | self.header["peripherals_text"] + "Wheel" + ", "
311 | else:
312 | self.header["peripherals_text"] += (
313 | controllers2[controller.get("type")] + ", "
314 | )
315 |
316 | other_peripherals = True
317 |
318 | if other_peripherals:
319 | # find a way to get rid of the "ZZ" part later
320 | self.header["peripherals_text"] = enc(
321 | "ZZ" + self.header["peripherals_text"][:-2], 88
322 | )
323 |
324 | for feature in s.find("wi-fi").findall("feature"):
325 | if "online" in feature.text:
326 | self.header["online_flag"] = u8(1)
327 | self.header["nintendo_wifi_connection_flag"] = u8(1)
328 |
329 | # what languages does this game support?
330 | languages = {
331 | "ZHCN": "chinese",
332 | "KO": "korean",
333 | "JA": "japanese",
334 | "EN": "english",
335 | "FR": "french",
336 | "ES": "spanish",
337 | "DE": "german",
338 | "IT": "italian",
339 | "NL": "dutch",
340 | }
341 | languages_list = s.find("languages").text.split(",")
342 |
343 | for l in languages.keys():
344 | if l in languages_list:
345 | self.header["language_{}_flag".format(languages[l])] = u8(1)
346 |
347 | # write the synopsis, and text wrap it properly
348 | try:
349 | wrap = textwrap.wrap(
350 | s.find("locale", {"lang": "EN"}).find("synopsis").text, 40
351 | )
352 |
353 | if len(wrap) <= 4:
354 | text_type = (
355 | "description" # put the synopsis at the top of the page
356 | )
357 | else:
358 | text_type = (
359 | "custom_field" # put the synopsis in the middle of the page
360 | )
361 |
362 | # let's shorten the synopsis until it fits
363 |
364 | synopsis_text = (
365 | s.find("locale", {"lang": "EN"})
366 | .find("synopsis")
367 | .text[:400]
368 | .replace("\n", "")
369 | .replace(" ", " ")
370 | .split(".")
371 | )
372 |
373 | if (
374 | len(s.find("locale", {"lang": "EN"}).find("synopsis").text)
375 | > 400
376 | ):
377 | try:
378 | synopsis_text = synopsis_text[:-1]
379 | synopsis_text[-1] += "."
380 | except:
381 | pass
382 |
383 | i = len("".join(textwrap.wrap(". ".join(synopsis_text), 40))) + 1
384 | j = len(synopsis_text)
385 |
386 | while i > 11:
387 | j -= 1
388 | sentences = ". ".join(synopsis_text[:j])
389 | if len(sentences) != 0:
390 | sentences += "."
391 | wrap = textwrap.wrap(sentences, 40)
392 | i = len(wrap)
393 |
394 | i = 1
395 |
396 | for w in wrap:
397 | self.header["{}_text_{}".format(text_type, i)] = enc(w, 82)
398 | i += 1
399 | if i == 10 and len(wrap) > 10:
400 | self.header["{}_text_{}".format(text_type, i)] += b"..."
401 | break
402 | except AttributeError:
403 | pass
404 |
405 | self.header["title"] = title = (
406 | s.find("locale", {"lang": "EN"}).find("title").text
407 | )
408 |
409 | # make separator in game name have a subtitle too
410 |
411 | if ": " in self.header["title"]:
412 | self.header["title"] = title.split(": ")[0]
413 | self.header["subtitle"] = enc(title.split(": ")[1], 62)
414 |
415 | elif " - " in self.header["title"]:
416 | self.header["title"] = title.split(" - ")[0]
417 | self.header["subtitle"] = enc(title.split(" - ")[1], 62)
418 |
419 | self.header["title"] = enc(self.header["title"], 62)
420 |
421 | try:
422 | self.header["genre_text"] = enc(
423 | s.find("genre").text.title().replace(",", ", "), 58
424 | )
425 |
426 | except AttributeError:
427 | pass
428 |
429 | players_local = s.find("input").get("players")
430 | players_online = s.find("wi-fi").get("players")
431 |
432 | if sys.argv[1] == "Wii":
433 | self.header["players_text"] = players_local
434 |
435 | if players_local != "1":
436 | self.header["players_text"] += "s"
437 |
438 | """if players_online != "0":
439 | self.header["players_text"] += " (Local), " + \
440 | players_online + \
441 | " (Online)"""
442 |
443 | self.header["players_text"] = enc(self.header["players_text"], 82)
444 |
445 | self.header["disclaimer_text"] = enc(
446 | 'Game information is provided by GameTDB. Press the\n"Purchase" button to get redirected '
447 | "to the GameTDB page.",
448 | 4800,
449 | )
450 |
451 | title_id = s.find("id").text
452 |
453 | # Download cover and convert to JPEG
454 | """print("Downloading cover art...")
455 | url = f"https://art.gametdb.com/ds/box/US/{title_id}.png"
456 | if sys.argv[1] == "Wii":
457 | url = f"https://art.gametdb.com/wii/cover/US/{title_id}.png"
458 | if sys.argv[1] == "3DS":
459 | url = f"https://art.gametdb.com/3ds/box/US/{title_id}.png"
460 |
461 | r = requests.get(
462 | url, headers={"User-Agent": "Nintendo Channel Info Downloader"})"""
463 |
464 | # Grab the cover image, and resize and center it
465 | if len(glob.glob(f"covers/{sys.argv[1]}/US/{title_id}*")) > 0:
466 | img = Image.new(mode="RGB", size=(384, 384), color=(255, 255, 255))
467 | cover_img = Image.open(
468 | glob.glob(f"covers/{sys.argv[1]}/US/{title_id}*")[0]
469 | )
470 | cover_img_w, cover_img_h = cover_img.size
471 | if sys.argv[1] != "3DS" and sys.argv[1] != "NDS":
472 | cover_img_resized = cover_img.resize(
473 | (int(cover_img_w * (384 / cover_img_h)), 384)
474 | )
475 | else:
476 | cover_img_resized = cover_img.resize(
477 | (384, int(cover_img_h * (384 / cover_img_w)))
478 | )
479 | cover_img_w, cover_img_h = cover_img_resized.size
480 | cover_rgb_img = img.convert("RGB")
481 | offset = ((384 - cover_img_w) // 2, (384 - cover_img_h) // 2)
482 | img.paste(cover_img_resized, offset)
483 | img.save(f"{sys.argv[1]}-{title_id}.jpg")
484 |
485 | return
486 |
487 | print("Error: Could not find {}.".format(sys.argv[2]))
488 | sys.exit(1)
489 |
490 | def write_jpegs(self):
491 | game_id = sys.argv[2]
492 |
493 | if len(glob.glob(f"{sys.argv[1]}-{game_id}*")) > 0:
494 | with open(glob.glob(f"{sys.argv[1]}-{game_id}*")[0], "rb") as cover:
495 | self.header["picture_offset"] = u32(self.offset_count())
496 | self.header["coverArt"] = cover.read()
497 | cover.seek(0, os.SEEK_END)
498 | self.header["picture_size"] = u32(cover.tell())
499 |
500 | # TODO: Figure out the ratings so I can write the correct images
501 | with open("ratings/ESRB/E-small.jpg", "rb") as rating:
502 | self.header["rating_picture_offset"] = u32(self.offset_count())
503 | self.header["ratingArt"] = rating.read()
504 | rating.seek(0, os.SEEK_END)
505 | self.header["rating_picture_size"] = u32(rating.tell())
506 |
507 | """with open("ratings/ESRB/rating.jpg", "rb") as detailed:
508 | self.header["rating_detail_picture_1_offset"] = u32(self.offset_count())
509 | self.header["detailed_rating_picture"] = detailed.read()
510 | detailed.seek(0, os.SEEK_END)
511 | self.header["rating_detail_picture_1_size"] = u32(detailed.tell())"""
512 |
513 | if os.path.exists(f"{sys.argv[1]}-{game_id}.jpg"):
514 | os.remove(f"{sys.argv[1]}-{game_id}.jpg")
515 |
516 | def write_file(self):
517 | self.header["filesize"] = u32(self.offset_count())
518 | id = int(binascii.hexlify(sys.argv[2][:4].encode("utf-8")).decode("utf-8"), 16)
519 |
520 | if sys.argv[1] == "NDS":
521 | id ^= 0x22222222
522 |
523 | elif sys.argv[1] == "3DS":
524 | id ^= 0x33333333
525 |
526 | elif sys.argv[1] == "WiiU":
527 | id ^= 0x44444444
528 |
529 | elif sys.argv[1] == "Switch":
530 | id ^= 0x55555555
531 |
532 | filename = str(id) + ".info"
533 |
534 | if os.path.exists(filename + "-1"):
535 | os.remove(filename + "-1")
536 |
537 | if os.path.exists(filename):
538 | os.remove(filename)
539 |
540 | self.writef = open(filename + "-1", "ab")
541 |
542 | for values in self.header.values():
543 | self.writef.write(values)
544 |
545 | self.writef.close()
546 |
547 | self.readf = open(filename + "-1", "rb")
548 |
549 | read = self.readf.read()
550 |
551 | self.writef2 = open(config["file_path"] + "/soft/US/en/" + filename, "wb")
552 |
553 | self.writef2.write(read)
554 | self.writef2.seek(8)
555 | self.writef2.write(
556 | binascii.unhexlify(format(binascii.crc32(read) & 0xFFFFFFFF, "08x"))
557 | )
558 |
559 | os.remove(filename + "-1")
560 |
561 | self.writef2.close()
562 |
563 |
564 | MakeInfo(GameTDB(True).parse())
565 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ninch_thumb.py:
--------------------------------------------------------------------------------
1 | import struct
2 | import os
3 |
4 |
5 | def u8(data):
6 | if not 0 <= data <= 255:
7 | print("u8 out of range: %s" % data, "INFO")
8 | data = 0
9 | return struct.pack(">B", data)
10 |
11 |
12 | def u16(data):
13 | if not 0 <= data <= 65535:
14 | print("u16 out of range: %s" % data, "INFO")
15 | data = 0
16 | return struct.pack(">H", data)
17 |
18 |
19 | def u32(data):
20 | if not 0 <= data <= 4294967295:
21 | print("u32 out of range: %s" % data, "INFO")
22 | data = 0
23 | return struct.pack(">I", data)
24 |
25 |
26 | class MakeThumb:
27 | def __init__(self):
28 | print("Generating File...")
29 | self.header = {}
30 | self.make_header()
31 | self.make_jpeg_table()
32 | self.write_jpegs()
33 | self.write_file()
34 |
35 | def offset_count(self):
36 | """
37 | This function returns the offset of where the selected table is
38 | """
39 | return sum(len(values) for values in list(self.header.values()) if values)
40 |
41 | def make_header(self):
42 | self.header["unknown"] = u16(0)
43 | self.header["version"] = u8(6)
44 | self.header["unknownRegion"] = u8(2)
45 | self.header["fileSize"] = u32(0)
46 | self.header[f"unknown1_1"] = u32(601820255)
47 | self.header[f"unknown1_2"] = u32(1)
48 | self.header[f"unknown1_3"] = u32(49)
49 | self.header[f"unknown1_4"] = u32(1)
50 | self.header[f"unknown1_5"] = u32(1252951207)
51 | self.header[f"number_of_images"] = u32(120)
52 |
53 | def make_jpeg_table(self):
54 | for i in range(120):
55 | self.header[f"jpegSize_{i}"] = u32(0)
56 | self.header[f"jpegOffset_{i}"] = u32(0)
57 |
58 | def write_jpegs(self):
59 | deadbeef = {0: 0xDE, 1: 0xAD, 2: 0xBE, 3: 0xEF}
60 | for i in range(120):
61 | with open("testing.jpeg", "rb") as image:
62 | self.header[f"jpegOffset_{i}"] = u32(self.offset_count())
63 | self.header[f"JPEGData{i}"] = image.read()
64 | counter = 0
65 | while self.offset_count() % 32 != 0:
66 | self.header[f"deadbeef_{i}_{counter}"] = u8(deadbeef[counter % 4])
67 | counter += 1
68 | # Seek to end of file to set filesize
69 | image.seek(0, os.SEEK_END)
70 | self.header[f"jpegSize_{i}"] = u32(image.tell())
71 | image.close()
72 |
73 | def write_file(self):
74 | # Now that all the file contents are written, calculate filesize
75 | self.header["fileSize"] = u32(self.offset_count())
76 |
77 | filename = "testing.thumb"
78 |
79 | if os.path.exists(filename):
80 | os.remove(filename)
81 |
82 | self.writef = open(filename, "ab")
83 |
84 | for values in self.header.values():
85 | self.writef.write(values)
86 |
87 | self.writef.close()
88 |
89 |
90 | MakeThumb()
91 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ninfile1.py:
--------------------------------------------------------------------------------
1 | import ninfile
2 | from kaitaistruct import KaitaiStream, BytesIO
3 |
4 | file = ninfile.NinchDllist.from_file("434968891.LZ")
5 |
6 | nintendo_channel_file = {}
7 |
8 | # header
9 |
10 | nintendo_channel_file["unknown"] = file.unknown
11 | nintendo_channel_file["version"] = file.version
12 | nintendo_channel_file["unknown_region"] = file.unknown_region
13 | nintendo_channel_file["filesize"] = file.filesize
14 | nintendo_channel_file["crc32"] = file.crc32
15 | nintendo_channel_file["dllistid"] = file.dllistid
16 | nintendo_channel_file["thumbnail_id"] = file.thumbnail_id
17 | nintendo_channel_file["country_code"] = file.country_code
18 | nintendo_channel_file["language_code"] = file.language_code
19 | nintendo_channel_file["unknown_2"] = file.unknown_2
20 | nintendo_channel_file["ratings_entry_number"] = file.ratings_entry_number
21 | nintendo_channel_file["ratings_table_offset"] = file.ratings_table_offset
22 | nintendo_channel_file["title_types_entry_number"] = file.title_types_entry_number
23 | nintendo_channel_file["title_types_table_offset"] = file.title_types_table_offset
24 | nintendo_channel_file["company_entry_number"] = file.company_entry_number
25 | nintendo_channel_file["company_table_offset"] = file.company_table_offset
26 | nintendo_channel_file["title_entry_number"] = file.title_entry_number
27 | nintendo_channel_file["title_table_offset"] = file.title_table_offset
28 | nintendo_channel_file["new_title_entry_number"] = file.new_title_entry_number
29 | nintendo_channel_file["new_title_table_offset"] = file.new_title_table_offset
30 | nintendo_channel_file["videos_1_entry_number"] = file.videos_1_entry_number
31 | nintendo_channel_file["videos_1_table_offset"] = file.videos_1_table_offset
32 | nintendo_channel_file["new_video_entry_number"] = file.new_video_entry_number
33 | nintendo_channel_file["new_video_table_offset"] = file.new_video_table_offset
34 | nintendo_channel_file["demos_entry_number"] = file.demos_entry_number
35 | nintendo_channel_file["demos_table_offset"] = file.demos_table_offset
36 | nintendo_channel_file["unknown_5"] = file.unknown_5
37 | nintendo_channel_file["unknown_6"] = file.unknown_6
38 | nintendo_channel_file[
39 | "recommendations_entry_number"
40 | ] = file.recommendations_entry_number
41 | nintendo_channel_file[
42 | "recommendations_table_offset"
43 | ] = file.recommendations_table_offset
44 | nintendo_channel_file["unknown_7"] = file.unknown_7
45 | nintendo_channel_file[
46 | "recent_recommendations_entry_number"
47 | ] = file.recent_recommendations_entry_number
48 | nintendo_channel_file[
49 | "recent_recommendations_table_offset"
50 | ] = file.recent_recommendations_table_offset
51 | nintendo_channel_file["unknown_8"] = file.unknown_8
52 | nintendo_channel_file["popular_videos_entry_number"] = file.popular_videos_entry_number
53 | nintendo_channel_file["popular_videos_table_offset"] = file.popular_videos_table_offset
54 | nintendo_channel_file[
55 | "detailed_ratings_entry_number"
56 | ] = file.detailed_ratings_entry_number
57 | nintendo_channel_file[
58 | "detailed_ratings_table_offset"
59 | ] = file.detailed_ratings_table_offset
60 | nintendo_channel_file["last_update"] = file.last_update
61 | nintendo_channel_file["unknown_9"] = file.unknown_9
62 | nintendo_channel_file["dl_url_ids"] = file.dl_url_ids
63 | nintendo_channel_file["unknown_10"] = file.unknown_10
64 |
65 | i = 0
66 |
67 | nintendo_channel_file["ratings_table"] = {}
68 |
69 | for f in file.ratings_table:
70 | nintendo_channel_file["ratings_table"][i] = {}
71 | nintendo_channel_file["ratings_table"][i]["rating_id"] = f.rating_id
72 | nintendo_channel_file["ratings_table"][i]["unknown"] = f.unknown
73 | nintendo_channel_file["ratings_table"][i]["age"] = f.age
74 | nintendo_channel_file["ratings_table"][i]["unknown2"] = f.unknown2
75 | nintendo_channel_file["ratings_table"][i]["jpeg_offset"] = f.jpeg_offset
76 | nintendo_channel_file["ratings_table"][i]["jpeg_size"] = f.jpeg_size
77 | nintendo_channel_file["ratings_table"][i]["title"] = f.title
78 | nintendo_channel_file["ratings_table"][i]["jpeg"] = f.jpeg
79 |
80 | i += 1
81 |
82 | i = 0
83 |
84 | nintendo_channel_file["title_types_table"] = {}
85 |
86 | for f in file.title_types_table:
87 | nintendo_channel_file["title_types_table"][i] = {}
88 | nintendo_channel_file["title_types_table"][i]["type_id"] = f.type_id
89 | nintendo_channel_file["title_types_table"][i]["console_model"] = f.console_model
90 | nintendo_channel_file["title_types_table"][i]["title"] = f.title
91 | nintendo_channel_file["title_types_table"][i]["group_id"] = f.group_id
92 | nintendo_channel_file["title_types_table"][i]["unknown"] = f.unknown
93 |
94 | i += 1
95 |
96 | i = 0
97 |
98 | nintendo_channel_file["company_table"] = {}
99 |
100 | for f in file.company_table:
101 | nintendo_channel_file["company_table"][i] = {}
102 | nintendo_channel_file["company_table"][i]["id"] = f.id
103 | nintendo_channel_file["company_table"][i]["dev_title"] = f.dev_title
104 | nintendo_channel_file["company_table"][i]["pub_title"] = f.pub_title
105 |
106 | i += 1
107 |
108 | i = 0
109 |
110 | nintendo_channel_file["title_table"] = {}
111 |
112 | for f in file.title_table:
113 | nintendo_channel_file["title_table"][i] = {}
114 | nintendo_channel_file["title_table"][i]["id"] = f.id
115 | nintendo_channel_file["title_table"][i]["title_id"] = f.title_id
116 | nintendo_channel_file["title_table"][i]["title_type"] = f.title_type
117 | nintendo_channel_file["title_table"][i]["genre"] = f.genre
118 | nintendo_channel_file["title_table"][i]["company_offset"] = f.company_offset
119 | nintendo_channel_file["title_table"][i]["release_date_year"] = f.release_date_year
120 | nintendo_channel_file["title_table"][i]["release_date_month"] = f.release_date_month
121 | nintendo_channel_file["title_table"][i]["release_date_day"] = f.release_date_day
122 | nintendo_channel_file["title_table"][i]["rating_id"] = f.rating_id
123 | nintendo_channel_file["title_table"][i]["unknown_4"] = f.unknown_4
124 | nintendo_channel_file["title_table"][i]["title"] = f.title
125 | nintendo_channel_file["title_table"][i]["subtitle"] = f.subtitle
126 | nintendo_channel_file["title_table"][i]["short_title"] = f.short_title
127 |
128 | i += 1
129 |
130 | i = 0
131 |
132 | nintendo_channel_file["new_title_table"] = {}
133 |
134 | for f in file.new_title_table:
135 | nintendo_channel_file["new_title_table"][i] = {}
136 | nintendo_channel_file["new_title_table"][i]["new_title_offset"] = f.new_title_offset
137 |
138 | i += 1
139 |
140 | i = 0
141 |
142 | nintendo_channel_file["videos_1_table"] = {}
143 |
144 | for f in file.videos_1_table:
145 | nintendo_channel_file["videos_1_table"][i] = {}
146 | nintendo_channel_file["videos_1_table"][i]["id"] = f.id
147 | nintendo_channel_file["videos_1_table"][i]["time_length"] = f.time_length
148 | nintendo_channel_file["videos_1_table"][i]["title_id"] = f.title_id
149 | nintendo_channel_file["videos_1_table"][i]["unknown"] = f.unknown
150 | nintendo_channel_file["videos_1_table"][i]["unknown_2"] = f.unknown_2
151 | nintendo_channel_file["videos_1_table"][i]["rating_id"] = f.rating_id
152 | nintendo_channel_file["videos_1_table"][i]["unknown_3"] = f.unknown_3
153 | nintendo_channel_file["videos_1_table"][i]["new_tag"] = f.new_tag
154 | nintendo_channel_file["videos_1_table"][i]["video_index"] = f.video_index
155 | nintendo_channel_file["videos_1_table"][i]["unknown_4"] = f.unknown_4
156 | nintendo_channel_file["videos_1_table"][i]["title"] = f.title
157 |
158 | i += 1
159 |
160 | i = 0
161 |
162 | nintendo_channel_file["new_video_table"] = {}
163 |
164 | for f in file.new_video_table:
165 | nintendo_channel_file["new_video_table"][i] = {}
166 | nintendo_channel_file["new_video_table"][i]["id"] = f.id
167 | nintendo_channel_file["new_video_table"][i]["unknown"] = f.unknown
168 | nintendo_channel_file["new_video_table"][i]["title_id"] = f.title_id
169 | nintendo_channel_file["new_video_table"][i]["unknown_2"] = f.unknown_2
170 | nintendo_channel_file["new_video_table"][i]["title"] = f.title
171 |
172 | i += 1
173 |
174 | i = 0
175 |
176 | nintendo_channel_file["demos_table"] = {}
177 |
178 | for f in file.demos_table:
179 | nintendo_channel_file["demos_table"][i] = {}
180 | nintendo_channel_file["demos_table"][i]["id"] = f.id
181 | nintendo_channel_file["demos_table"][i]["title"] = f.title
182 | nintendo_channel_file["demos_table"][i]["subtitle"] = f.subtitle
183 | nintendo_channel_file["demos_table"][i]["titleid"] = f.titleid
184 | nintendo_channel_file["demos_table"][i]["company_offset"] = f.company_offset
185 | nintendo_channel_file["demos_table"][i]["removal_year"] = f.removal_year
186 | nintendo_channel_file["demos_table"][i]["removal_month"] = f.removal_month
187 | nintendo_channel_file["demos_table"][i]["removal_day"] = f.removal_day
188 | nintendo_channel_file["demos_table"][i]["unknown"] = f.unknown
189 | nintendo_channel_file["demos_table"][i]["rating_id"] = f.rating_id
190 | nintendo_channel_file["demos_table"][i]["new_tag"] = f.new_tag
191 | nintendo_channel_file["demos_table"][i]["new_tag_index"] = f.new_tag_index
192 | nintendo_channel_file["demos_table"][i]["unknown_2"] = f.unknown_2
193 |
194 | i += 1
195 |
196 | i = 0
197 |
198 | nintendo_channel_file["recommendations_table"] = {}
199 |
200 | for f in file.recommendations_table:
201 | nintendo_channel_file["recommendations_table"][i] = {}
202 | nintendo_channel_file["recommendations_table"][i][
203 | "recommendation_title_offset"
204 | ] = f.recommendation_title_offset
205 |
206 | i += 1
207 |
208 | nintendo_channel_file["recent_recommendations_table"] = {}
209 |
210 | for f in file.recent_recommendations_table:
211 | nintendo_channel_file["recent_recommendations_table"][i] = {}
212 | nintendo_channel_file["recent_recommendations_table"][i][
213 | "recent_recommendation_title_offset"
214 | ] = f.recent_recommendation_title_offset
215 | nintendo_channel_file["recent_recommendations_table"][i]["unknown"] = f.unknown
216 |
217 | i += 1
218 |
219 | i = 0
220 |
221 | nintendo_channel_file["popular_videos_table"] = {}
222 |
223 | for f in file.popular_videos_table:
224 | nintendo_channel_file["popular_videos_table"][i] = {}
225 | nintendo_channel_file["popular_videos_table"][i]["id"] = f.id
226 | nintendo_channel_file["popular_videos_table"][i]["time_length"] = f.time_length
227 | nintendo_channel_file["popular_videos_table"][i]["title_id"] = f.title_id
228 | nintendo_channel_file["popular_videos_table"][i]["bar_color"] = f.bar_color
229 | nintendo_channel_file["popular_videos_table"][i]["unknown_2"] = f.unknown_2
230 | nintendo_channel_file["popular_videos_table"][i]["rating_id"] = f.rating_id
231 | nintendo_channel_file["popular_videos_table"][i]["unknown_3"] = f.unknown_3
232 | nintendo_channel_file["popular_videos_table"][i]["video_rank"] = f.video_rank
233 | nintendo_channel_file["popular_videos_table"][i]["unknown_4"] = f.unknown_4
234 | nintendo_channel_file["popular_videos_table"][i]["title"] = f.title
235 |
236 | i += 1
237 |
238 | i = 0
239 |
240 | nintendo_channel_file["detailed_ratings_table"] = {}
241 |
242 | for f in file.detailed_ratings_table:
243 | nintendo_channel_file["detailed_ratings_table"][i] = {}
244 | nintendo_channel_file["detailed_ratings_table"][i]["rating_group"] = f.rating_group
245 | nintendo_channel_file["detailed_ratings_table"][i]["rating_id"] = f.rating_id
246 | nintendo_channel_file["detailed_ratings_table"][i]["title"] = f.title
247 |
248 | i += 1
249 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ninfile2.py:
--------------------------------------------------------------------------------
1 | import lxml.etree as et
2 | import ninfile1
3 | import os
4 | import requests
5 | import struct
6 | import zipfile
7 |
8 | """Pack integers to specific type."""
9 |
10 |
11 | # Unsigned integers
12 |
13 |
14 | def u8(data):
15 | if not 0 <= data <= 255:
16 | log("u8 out of range: %s" % data, "INFO")
17 | data = 0
18 | return struct.pack(">B", data)
19 |
20 |
21 | def u16(data):
22 | if not 0 <= data <= 65535:
23 | log("u16 out of range: %s" % data, "INFO")
24 | data = 0
25 | return struct.pack(">H", data)
26 |
27 |
28 | def u32(data):
29 | if not 0 <= data <= 4294967295:
30 | log("u32 out of range: %s" % data, "INFO")
31 | data = 0
32 | return struct.pack(">I", data)
33 |
34 |
35 | def u32_littleendian(data):
36 | if not 0 <= data <= 4294967295:
37 | log("u32 little endian out of range: %s" % data, "INFO")
38 | data = 0
39 | return struct.pack("b", data)
50 |
51 |
52 | def s16(data):
53 | if not -32768 <= data <= 32767:
54 | log("s16 out of range: %s" % data, "INFO")
55 | data = 0
56 | return struct.pack(">h", data)
57 |
58 |
59 | def s32(data):
60 | if not -2147483648 <= data <= 2147483647:
61 | log("s32 out of range: %s" % data, "INFO")
62 | data = 0
63 | return struct.pack(">i", data)
64 |
65 |
66 | def strIDToint(id):
67 | return (ord(id[0]) << 24 | ord(id[1]) << 16 | ord(id[2]) << 8) ^ 0x52433234
68 |
69 |
70 | def intTostrID(id):
71 | id ^= 0x52433234
72 | return chr(id >> 24) + chr(id >> 16 & 0xFF) + chr(id >> 8 & 0xFF) + chr(id & 0xFF)
73 |
74 |
75 | class GameTDB:
76 | def __init__(self, cache):
77 | self.databases = {
78 | "Wii": ["wii", None],
79 | "3DS": ["3ds", None],
80 | "NDS": ["ds", None],
81 | "WiiU": ["wiiu", None],
82 | "Switch": ["switch", None],
83 | }
84 | self.download()
85 |
86 | def download(self):
87 | for k, v in self.databases.items():
88 | print("Downloading {} Database from GameTDB...".format(k))
89 | filename = v[0] + "tdb"
90 | if not os.path.exists(filename + ".xml"):
91 | url = "https://www.gametdb.com/{}".format(filename + ".zip")
92 | # It's blocked for the "python-requests" user-agent to encourage setting a different user-agent for different apps, to get an idea of the origin of the requests. (according to the GameTDB admin).
93 | r = requests.get(
94 | url, headers={"User-Agent": "Nintendo Channel Info Downloader"}
95 | )
96 | open(filename + ".zip", "wb").write(r.content)
97 | self.zip = zipfile.ZipFile(filename + ".zip")
98 | self.zip.extractall(".")
99 | self.zip.close()
100 |
101 | def parse(self):
102 | for k, v in self.databases.items():
103 | filename = v[0] + "tdb"
104 |
105 | print("Loading {}...".format(k))
106 | v[1] = et.parse(filename + ".xml")
107 |
108 | return self.databases
109 |
110 |
111 | class NintendoChannel:
112 | def __init__(self, ninfile):
113 | self.dictionaries = {}
114 | self.ninfile = ninfile
115 | self.build()
116 | self.write()
117 |
118 | def build(self):
119 | print("Generating list file...")
120 |
121 | self.dictionaries["header"] = self.make_header()
122 | self.dictionaries["ratings_table"] = self.make_ratings_table()
123 | self.dictionaries["title_types_table"] = self.make_title_types_table()
124 | self.dictionaries["company_table"] = self.make_company_table()
125 | self.dictionaries["title_table"] = self.make_title_table()
126 | self.dictionaries["new_title_table"] = self.make_new_title_table()
127 | self.dictionaries["videos_1_table"] = self.make_videos_1_table()
128 | self.dictionaries["new_video_table"] = self.make_new_video_table()
129 | self.dictionaries["demos_table"] = self.make_demos_table()
130 | self.dictionaries["recommendations_table"] = self.make_recommendations_table()
131 | self.dictionaries[
132 | "recent_recommendations_table"
133 | ] = self.make_recent_recommendations_table()
134 | self.dictionaries["popular_videos_table"] = self.make_popular_videos_table()
135 | self.dictionaries["jpeg"] = self.make_jpeg()
136 | self.dictionaries["detailed_ratings_table"] = self.make_detailed_ratings_table()
137 |
138 | self.dictionaries["header"]["filesize"] = u32(self.offset_count1())
139 |
140 | def offset_count1(self):
141 | return sum(
142 | len(values)
143 | for dictionary in self.dictionaries
144 | for values in list(self.dictionaries[dictionary].values())
145 | if values
146 | )
147 |
148 | def offset_count2(self, dictionary):
149 | return sum(len(values) for values in list(dictionary.values()) if values)
150 |
151 | def offset_count(self, dictionary):
152 | return self.offset_count1() + self.offset_count2(dictionary)
153 |
154 | def write(self):
155 | filename = "434968893.LZ"
156 |
157 | if os.path.exists(filename):
158 | os.remove(filename)
159 |
160 | with open(filename, "ab+") as f:
161 | for dictionary in self.dictionaries:
162 | for v in self.dictionaries[dictionary].values():
163 | f.write(v)
164 |
165 | def make_header(self):
166 | header = {}
167 |
168 | header["unknown"] = u16(self.ninfile["unknown"])
169 | header["version"] = u8(self.ninfile["version"])
170 | header["unknown_region"] = u8(self.ninfile["unknown_region"])
171 | header["filesize"] = u32(0)
172 | header["crc32"] = u32(self.ninfile["crc32"])
173 | header["dllistid"] = u32(self.ninfile["dllistid"])
174 | header["thumbnail_id"] = u32(self.ninfile["thumbnail_id"])
175 | header["country_code"] = u32(self.ninfile["country_code"])
176 | header["language_code"] = u32(self.ninfile["language_code"])
177 |
178 | for i in range(0, 9):
179 | header["unknown_2_" + str(i)] = u8(self.ninfile["unknown_2"][i])
180 |
181 | header["ratings_entry_number"] = u32(len(self.ninfile["ratings_table"]))
182 | header["ratings_table_offset"] = u32(0)
183 | header["title_types_entry_number"] = u32(len(self.ninfile["title_types_table"]))
184 | header["title_types_table_offset"] = u32(0)
185 | header["company_entry_number"] = u32(len(self.ninfile["company_table"]))
186 | header["company_table_offset"] = u32(0)
187 | header["title_entry_number"] = u32(len(self.ninfile["title_table"]))
188 | header["title_table_offset"] = u32(0)
189 | header["new_title_entry_number"] = u32(len(self.ninfile["new_title_table"]))
190 | header["new_title_table_offset"] = u32(0)
191 | header["videos_1_entry_number"] = u32(len(self.ninfile["videos_1_table"]))
192 | header["videos_1_table_offset"] = u32(0)
193 | header["new_video_entry_number"] = u32(len(self.ninfile["new_video_table"]))
194 | header["new_video_table_offset"] = u32(0)
195 | header["demos_entry_number"] = u32(len(self.ninfile["demos_table"]))
196 | header["demos_table_offset"] = u32(0)
197 | header["unknown_5"] = u32(self.ninfile["unknown_5"])
198 | header["unknown_6"] = u32(self.ninfile["unknown_6"])
199 | header["recommendations_entry_number"] = u32(
200 | len(self.ninfile["recommendations_table"])
201 | )
202 | header["recommendations_table_offset"] = u32(0)
203 |
204 | for i in range(0, 4):
205 | header["unknown_7_" + str(i)] = u32(self.ninfile["unknown_7"][i])
206 |
207 | header["recent_recommendations_entry_number"] = u32(
208 | len(self.ninfile["recent_recommendations_table"])
209 | )
210 | header["recent_recommendations_table_offset"] = u32(0)
211 |
212 | for i in range(0, 2):
213 | header["unknown_8_" + str(i)] = u32(self.ninfile["unknown_8"][i])
214 |
215 | header["popular_videos_entry_number"] = u32(
216 | len(self.ninfile["popular_videos_table"])
217 | )
218 | header["popular_videos_table_offset"] = u32(0)
219 | header["detailed_ratings_entry_number"] = u32(
220 | len(self.ninfile["detailed_ratings_table"])
221 | )
222 | header["detailed_ratings_table_offset"] = u32(0)
223 | header["last_update"] = (
224 | self.ninfile["last_update"].encode("utf-16be").rjust(62, b"\x00")
225 | )
226 |
227 | for i in range(0, 3):
228 | header["unknown_9_" + str(i)] = u8(self.ninfile["unknown_9"][i])
229 |
230 | for i in range(0, 5):
231 | header["dl_url_ids_" + str(i)] = (
232 | self.ninfile["dl_url_ids"][i].encode("utf-8").rjust(256, b"\x00")
233 | )
234 |
235 | for i in range(0, 4):
236 | header["unknown_10_" + str(i)] = u8(self.ninfile["unknown_10"][i])
237 |
238 | return header
239 |
240 | def make_ratings_table(self):
241 | ratings_table = {}
242 |
243 | i = 0
244 |
245 | self.dictionaries["header"]["ratings_table_offset"] = u32(self.offset_count1())
246 |
247 | for r in self.ninfile["ratings_table"]:
248 | r = self.ninfile["ratings_table"][r]
249 |
250 | i += 1
251 |
252 | ratings_table["rating_id_" + str(i)] = u8(r["rating_id"])
253 | ratings_table["unknown_" + str(i)] = u8(r["unknown"])
254 | ratings_table["age_" + str(i)] = u8(r["age"])
255 | ratings_table["unknown2_" + str(i)] = u8(r["unknown2"])
256 | ratings_table["jpeg_offset_" + str(i)] = u32(r["jpeg_offset"])
257 | ratings_table["jpeg_size_" + str(i)] = u32(r["jpeg_size"])
258 | ratings_table["title_" + str(i)] = (
259 | r["title"].encode("utf-16be").rjust(22, b"\x00")
260 | )
261 |
262 | return ratings_table
263 |
264 | def make_title_types_table(self):
265 | title_types_table = {}
266 |
267 | i = 0
268 |
269 | self.dictionaries["header"]["title_types_table_offset"] = u32(
270 | self.offset_count1()
271 | )
272 |
273 | for t in self.ninfile["title_types_table"]:
274 | t = self.ninfile["title_types_table"][t]
275 |
276 | i += 1
277 |
278 | title_types_table["type_id_" + str(i)] = u8(t["type_id"])
279 | title_types_table["console_model_" + str(i)] = (
280 | t["console_model"].encode("utf-8").rjust(3, b"\x00")
281 | )
282 | title_types_table["title_" + str(i)] = (
283 | t["title"].encode("utf-16be").rjust(102, b"\x00")
284 | )
285 | title_types_table["group_id_" + str(i)] = u8(t["group_id"])
286 | title_types_table["unknown_" + str(i)] = u8(t["unknown"])
287 |
288 | return title_types_table
289 |
290 | def make_company_table(self):
291 | company_table = {}
292 |
293 | i = 0
294 |
295 | self.dictionaries["header"]["company_table_offset"] = u32(self.offset_count1())
296 |
297 | for c in self.ninfile["company_table"]:
298 | c = self.ninfile["company_table"][c]
299 |
300 | i += 1
301 |
302 | company_table["id_" + str(i)] = u32(c["id"])
303 | company_table["dev_title_" + str(i)] = (
304 | c["dev_title"].encode("utf-16be").rjust(62, b"\x00")
305 | )
306 | company_table["pub_title_" + str(i)] = (
307 | c["pub_title"].encode("utf-16be").rjust(62, b"\x00")
308 | )
309 |
310 | return company_table
311 |
312 | def make_title_table(self):
313 | title_table = {}
314 |
315 | i = 0
316 |
317 | self.dictionaries["header"]["title_table_offset"] = u32(self.offset_count1())
318 |
319 | for t in self.ninfile["title_table"]:
320 | t = self.ninfile["title_table"][t]
321 |
322 | i += 1
323 |
324 | title_table["id_" + str(i)] = u32(t["id"])
325 | title_table["title_id_" + str(i)] = (
326 | t["title_id"].encode("utf-8").rjust(4, b"\x00")
327 | )
328 | title_table["title_type_" + str(i)] = u8(t["title_type"])
329 |
330 | for j in range(0, 3):
331 | title_table["genre_" + str(i) + "_" + str(j)] = u8(t["genre"][j])
332 |
333 | title_table["company_offset_" + str(i)] = u32(t["company_offset"])
334 | title_table["release_date_year_" + str(i)] = u16(t["release_date_year"])
335 | title_table["release_date_month_" + str(i)] = u8(t["release_date_month"])
336 | title_table["release_date_day_" + str(i)] = u8(t["release_date_day"])
337 | title_table["rating_id_" + str(i)] = u8(t["rating_id"])
338 |
339 | for j in range(0, 29):
340 | title_table["unknown_4_" + str(i) + "_" + str(j)] = u8(
341 | t["unknown_4"][j]
342 | )
343 |
344 | title_table["title_" + str(i)] = (
345 | t["title"].encode("utf-16be").rjust(62, b"\x00")
346 | )
347 | title_table["subtitle_" + str(i)] = (
348 | t["subtitle"].encode("utf-16be").rjust(62, b"\x00")
349 | )
350 | title_table["short_title_" + str(i)] = (
351 | t["short_title"].encode("utf-16be").rjust(62, b"\x00")
352 | )
353 |
354 | return title_table
355 |
356 | def make_new_title_table(self):
357 | new_title_table = {}
358 |
359 | i = 0
360 |
361 | self.dictionaries["header"]["new_title_table_offset"] = u32(
362 | self.offset_count1()
363 | )
364 |
365 | for n in self.ninfile["new_title_table"]:
366 | n = self.ninfile["new_title_table"][n]
367 |
368 | i += 1
369 |
370 | new_title_table["new_title_offset_" + str(i)] = u32(n["new_title_offset"])
371 |
372 | return new_title_table
373 |
374 | def make_videos_1_table(self):
375 | videos_1_table = {}
376 |
377 | i = 0
378 |
379 | self.dictionaries["header"]["videos_1_table_offset"] = u32(self.offset_count1())
380 |
381 | for v in self.ninfile["videos_1_table"]:
382 | v = self.ninfile["videos_1_table"][v]
383 |
384 | i += 1
385 |
386 | videos_1_table["id_" + str(i)] = u32(v["id"])
387 | videos_1_table["time_length_" + str(i)] = u16(v["time_length"])
388 | videos_1_table["title_id_" + str(i)] = u32(v["title_id"])
389 |
390 | for j in range(0, 15):
391 | videos_1_table["unknown_" + str(i) + "_" + str(j)] = u8(v["unknown"][j])
392 |
393 | videos_1_table["unknown2_" + str(i)] = u8(v["unknown_2"])
394 | videos_1_table["rating_id_" + str(i)] = u8(v["rating_id"])
395 | videos_1_table["unknown3_" + str(i)] = u8(v["unknown_3"])
396 | videos_1_table["new_tag_" + str(i)] = u8(v["new_tag"])
397 | videos_1_table["video_index_" + str(i)] = u8(v["video_index"])
398 |
399 | for j in range(0, 2):
400 | videos_1_table["unknown4_" + str(i) + "_" + str(j)] = u8(
401 | v["unknown_4"][j]
402 | )
403 |
404 | videos_1_table["title_" + str(i)] = (
405 | v["title"].encode("utf-16be").rjust(246, b"\x00")
406 | )
407 |
408 | return videos_1_table
409 |
410 | def make_new_video_table(self):
411 | new_video_table = {}
412 |
413 | i = 0
414 |
415 | self.dictionaries["header"]["new_video_table_offset"] = u32(
416 | self.offset_count1()
417 | )
418 |
419 | for n in self.ninfile["new_video_table"]:
420 | n = self.ninfile["new_video_table"][n]
421 |
422 | i += 1
423 |
424 | new_video_table["id_" + str(i)] = u32(n["id"])
425 | new_video_table["unknown_" + str(i)] = u16(n["unknown"])
426 | new_video_table["title_id_" + str(i)] = u32(n["title_id"])
427 |
428 | for j in range(0, 18):
429 | new_video_table["unknown2_" + str(i) + "_" + str(j)] = u8(
430 | n["unknown_2"][j]
431 | )
432 |
433 | new_video_table["title_" + str(i)] = (
434 | n["title"].encode("utf-16be").rjust(204, b"\x00")
435 | )
436 |
437 | return new_video_table
438 |
439 | def make_demos_table(self):
440 | demos_table = {}
441 |
442 | i = 0
443 |
444 | self.dictionaries["header"]["demos_table_offset"] = u32(self.offset_count1())
445 |
446 | for d in self.ninfile["demos_table"]:
447 | d = self.ninfile["demos_table"][d]
448 |
449 | i += 1
450 |
451 | demos_table["id_" + str(i)] = u32(d["id"])
452 | demos_table["title_" + str(i)] = (
453 | d["title"].encode("utf-16be").rjust(62, b"\x00")
454 | )
455 | demos_table["subtitle_" + str(i)] = (
456 | d["subtitle"].encode("utf-16be").rjust(62, b"\x00")
457 | )
458 | demos_table["titleid_" + str(i)] = u32(d["titleid"])
459 | demos_table["company_offset_" + str(i)] = u32(d["company_offset"])
460 | demos_table["removal_year_" + str(i)] = u16(d["removal_year"])
461 | demos_table["removal_month_" + str(i)] = u8(d["removal_month"])
462 | demos_table["removal_day_" + str(i)] = u8(d["removal_day"])
463 | demos_table["unknown_" + str(i)] = u32(d["unknown"])
464 | demos_table["rating_id_" + str(i)] = u8(d["rating_id"])
465 | demos_table["new_tag_" + str(i)] = u8(d["new_tag"])
466 | demos_table["new_tag_index_" + str(i)] = u8(d["new_tag_index"])
467 |
468 | for j in range(0, 205):
469 | demos_table["unknown2_" + str(i) + "_" + str(j)] = u8(d["unknown_2"][j])
470 |
471 | return demos_table
472 |
473 | def make_recommendations_table(self):
474 | recommendations_table = {}
475 |
476 | i = 0
477 |
478 | self.dictionaries["header"]["recommendations_table_offset"] = u32(
479 | self.offset_count1()
480 | )
481 |
482 | for r in self.ninfile["recommendations_table"]:
483 | r = self.ninfile["recommendations_table"][r]
484 |
485 | i += 1
486 |
487 | recommendations_table["recommendation_table_offset_" + str(i)] = u32(
488 | r["recommendation_title_offset"]
489 | )
490 |
491 | return recommendations_table
492 |
493 | def make_recent_recommendations_table(self):
494 | recent_recommendations_table = {}
495 |
496 | i = 0
497 |
498 | self.dictionaries["header"]["recent_recommendations_table_offset"] = u32(
499 | self.offset_count1()
500 | )
501 |
502 | for r in self.ninfile["recent_recommendations_table"]:
503 | r = self.ninfile["recent_recommendations_table"][r]
504 |
505 | i += 1
506 |
507 | recent_recommendations_table[
508 | "recent_recommendation_title_offset_" + str(i)
509 | ] = u32(r["recent_recommendation_title_offset"])
510 | recent_recommendations_table["unknown_" + str(i)] = u16(r["unknown"])
511 |
512 | return recent_recommendations_table
513 |
514 | def make_popular_videos_table(self):
515 | popular_videos_table = {}
516 |
517 | i = 0
518 |
519 | self.dictionaries["header"]["popular_videos_table_offset"] = u32(
520 | self.offset_count1()
521 | )
522 |
523 | for p in self.ninfile["popular_videos_table"]:
524 | p = self.ninfile["popular_videos_table"][p]
525 |
526 | i += 1
527 |
528 | popular_videos_table["id_" + str(i)] = u32(p["id"])
529 | popular_videos_table["time_length_" + str(i)] = u16(p["time_length"])
530 | popular_videos_table["title_id_" + str(i)] = u32(p["title_id"])
531 | popular_videos_table["bar_color_" + str(i)] = u8(p["bar_color"])
532 |
533 | for j in range(0, 15):
534 | popular_videos_table["unknown2_" + str(i) + "_" + str(j)] = u8(
535 | p["unknown_2"][j]
536 | )
537 |
538 | popular_videos_table["rating_id_" + str(i)] = u8(p["rating_id"])
539 | popular_videos_table["unknown3_" + str(i)] = u8(p["unknown_3"])
540 | popular_videos_table["video_rank_" + str(i)] = u8(p["video_rank"])
541 | popular_videos_table["unknown4_" + str(i)] = u8(p["unknown_4"])
542 | popular_videos_table["title_" + str(i)] = (
543 | p["title"].encode("utf-16be").rjust(204, b"\x00")
544 | )
545 |
546 | return popular_videos_table
547 |
548 | def make_detailed_ratings_table(self):
549 | detailed_ratings_table = {}
550 |
551 | i = 0
552 |
553 | self.dictionaries["header"]["detailed_ratings_table_offset"] = u32(
554 | self.offset_count1()
555 | )
556 |
557 | for d in self.ninfile["detailed_ratings_table"]:
558 | d = self.ninfile["detailed_ratings_table"][d]
559 |
560 | i += 1
561 |
562 | detailed_ratings_table["rating_group_" + str(i)] = u8(d["rating_group"])
563 | detailed_ratings_table["rating_id_" + str(i)] = u8(d["rating_id"])
564 | detailed_ratings_table["title_" + str(i)] = (
565 | d["title"].encode("utf-16be").rjust(204, b"\x00")
566 | )
567 |
568 | return detailed_ratings_table
569 |
570 | def deadbeef(self, i):
571 | k = 0
572 |
573 | while (self.offset_count(self.jpeg) % 32) != 0:
574 | bytes = {0: 0xDE, 1: 0xAD, 2: 0xBE, 3: 0xEF}
575 |
576 | self.jpeg["deadbeef_" + str(i) + "_" + str(k)] = u8(bytes[k % 4])
577 |
578 | k += 1
579 |
580 | def make_jpeg(self):
581 | self.jpeg = {}
582 |
583 | i = 0
584 |
585 | for j in self.ninfile["ratings_table"]:
586 | j = self.ninfile["ratings_table"][j]
587 |
588 | if j["jpeg_offset"] != 0:
589 | i += 1
590 |
591 | self.deadbeef(i)
592 |
593 | self.jpeg["jpeg_" + str(i)] = j["jpeg"]
594 |
595 | self.deadbeef(i)
596 |
597 | return self.jpeg
598 |
599 |
600 | NintendoChannel(ninfile1.nintendo_channel_file)
601 |
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/BBFC/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/BBFC/12.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/BBFC/15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/BBFC/15.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/BBFC/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/BBFC/18.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/BBFC/PG.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/BBFC/PG.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/BBFC/U.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/BBFC/U.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/A.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/A.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/B.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/B.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/C.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/C.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/D.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/D.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/Z.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/Z.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/education.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/education.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/CERO/scheduled.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/CERO/scheduled.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/E-small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/E-small.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/E.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/E.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/E10-small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/E10-small.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/E10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/E10.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/EC.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/EC.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/M.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/M.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/T.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/T.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/maycontain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/maycontain.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/ESRB/visitesrb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/ESRB/visitesrb.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/.DS_Store
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/12.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/16.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/18.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/3.jpg
--------------------------------------------------------------------------------
/Channels/Nintendo_Channel/ratings/PEGI/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/Nintendo_Channel/ratings/PEGI/7.jpg
--------------------------------------------------------------------------------
/Channels/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Channels/__init__.py
--------------------------------------------------------------------------------
/Games/My_Aquarium/myaquarium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | # ===========================================================================
5 | # MY AQUARIUM - AQUARIUM ATTACHMENT GENERATOR
6 | # VERSION 1.0
7 | # AUTHORS: JOHN PANSERA
8 | # ****************************************************************************
9 | # Copyright (c) 2015-2023 RiiConnect24, and its (Lead) Developers
10 | # ===========================================================================
11 |
12 | import binascii
13 | import collections
14 | import io
15 | import struct
16 |
17 | bytes = 0
18 |
19 | print "My Aquarium Custom Attachment Generator"
20 | print "By John Pansera / Version 1.0\n"
21 |
22 |
23 | def u8(data):
24 | if data < 0 or data > 255:
25 | print "[+] Invalid Entry: %s" % data
26 | exit()
27 | return struct.pack(">B", data)
28 |
29 |
30 | def u16(data):
31 | if data < 0 or data > 65535:
32 | print "[+] Invalid Entry: %s" % data
33 | exit()
34 | return struct.pack(">H", data)
35 |
36 |
37 | def u32(data):
38 | if data < 0 or data > 4294967295:
39 | print "[+] Invalid Entry: %s" % data
40 | exit()
41 | return struct.pack(">I", data)
42 |
43 |
44 | def pad(amnt):
45 | buffer = ""
46 | for _ in range(amnt): buffer += "\0"
47 | return buffer
48 |
49 |
50 | print "AQUARIUM SIZE"
51 | print "0 - Small"
52 | print "1 - Medium"
53 | print "2 - Large"
54 | print "\n"
55 | aquarium_size = raw_input('Enter Selection: ')
56 | print "GLASS TYPE"
57 | print "0 - Normal"
58 | print "1 - Gift 1"
59 | print "2 - Gift 2"
60 | print "3 - Special Date 1"
61 | print "4 - Special Date 2"
62 | print "5 - Special Date 3"
63 | print "\n"
64 | glass_type = raw_input('Enter Selection : ')
65 | print "FLOOR TYPE"
66 | print "0 - River Bed"
67 | print "1 - Sand"
68 | print "2 - White Sand"
69 | print "3 - Gravel 1"
70 | print "4 - Gravel 2"
71 | print "\n"
72 | floor_type = raw_input('Enter Selection : ')
73 | print "BACKGROUND TYPE"
74 | print "0 - Underwater Theme"
75 | print "1 - Emerald Theme"
76 | print "2 - Coral Theme"
77 | print "3 - Quiet Ocean Theme"
78 | print "4 - Riverbed Theme"
79 | print "\n"
80 | background_type = raw_input('Enter Selection : ')
81 | print "LIGHT TYPE"
82 | print "0 - Normal"
83 | print "1 - Brightly Lit"
84 | print "2 - Very Bright"
85 | print "3 - Blue"
86 | print "4 - Green"
87 | print "5 - Orange"
88 | print "6 - Spotted"
89 | print "\n"
90 | light_type = raw_input('Enter Selection : ')
91 | print "\nSPECIAL DATE 1"
92 | specialdate1_month = raw_input('Enter Month Number : ')
93 | specialdate1_day = raw_input('Enter Day Number : ')
94 | print "\nSPECIAL DATE 2"
95 | specialdate2_month = raw_input('Enter Month Number : ')
96 | specialdate2_day = raw_input('Enter Day Number : ')
97 | print "\nSPECIAL DATE 3"
98 | specialdate3_month = raw_input('Enter Month Number : ')
99 | specialdate3_day = raw_input('Enter Day Number : ')
100 | print "\n"
101 | print "NOTE: This script does not currently generate objects in the tank"
102 |
103 | header = collections.OrderedDict()
104 | header["unknown"] = u8(0) # Version?
105 | header["aquarium_size"] = u8(int(aquarium_size))
106 | header["glass_type"] = u8(int(glass_type))
107 | header["floor_type"] = u8(int(floor_type))
108 | header["background_type"] = u8(int(background_type))
109 | header["light_type"] = u8(int(light_type))
110 | header["unknown_1"] = u16(0)
111 | header["breeding_date_counter"] = u32(0)
112 | header["unknown_date_counter"] = u32(0)
113 | header["special_date_1_padding"] = u8(0)
114 | header["special_date_1_month"] = u8(int(specialdate1_month))
115 | header["special_date_1_day"] = u8(int(specialdate1_day))
116 | header["special_date_1_padding_1"] = u8(0)
117 | header["special_date_2_padding"] = u8(0)
118 | header["special_date_2_month"] = u8(int(specialdate2_month))
119 | header["special_date_2_day"] = u8(int(specialdate2_day))
120 | header["special_date_2_padding_2"] = u8(0)
121 | header["special_date_3_padding"] = u8(0)
122 | header["special_date_3_month"] = u8(int(specialdate3_month))
123 | header["special_date_3_day"] = u8(int(specialdate3_day))
124 | header["special_date_3_padding_1"] = u8(0)
125 |
126 | if raw_input('Would you like to add fish? [y/n] ') is 'y':
127 | amnt = int(raw_input('Enter amount of fish to add [MAX 15]: '))
128 | if amnt <= 15:
129 | print "\n"
130 | print "FISH SELECTION [0-39]"
131 | print "Note: There are 40 different types of fish, however there are more listed here (found in the game's files - they may or may not be able to be used)"
132 | print "01 Achilles Tang"
133 | print "02 Asian Arowana"
134 | print "03 Red Asian Arowana"
135 | print "04 Blueface Angelfish"
136 | print "05 Emperor Angelfish"
137 | print "06 Yellow Piranha"
138 | print "07 Freshwater Angelfish"
139 | print "08 Ocellaris Clownfish"
140 | print "09 Blue Jellyfish"
141 | print "10 Clown Killifish"
142 | print "11 Clown Loach"
143 | print "12 Pakistani Loach"
144 | print "13 Sea Angel"
145 | print "14 Golden Gourami"
146 | print "15 Dwarf Hawkfish"
147 | print "16 Humpback Grouper"
148 | print "17 Coral Grouper"
149 | print "18 Killifish"
150 | print "19 Papuan Jellyfish"
151 | print "20 Checkered Barb"
152 | print "21 Jellyfish Choukurage"
153 | print "22 Tinkeri Butterfly"
154 | print "23 Corydoras Narcissus"
155 | print "24 Flagtail Surgeonfish"
156 | print "25 Neon Tetra"
157 | print "26 Cardinal Tetra"
158 | print "27 Pastaza Corydoras"
159 | print "28 Red Lionfish"
160 | print "29 Lyretail Hogfish"
161 | print "30 Wrasse"
162 | print "31 Blue Gourami"
163 | print "32 Black Molly"
164 | print "33 Blue Grass Guppy"
165 | print "34 Red Grass Guppy"
166 | print "35 Blue Discus"
167 | print "36 Betta"
168 | print "37 Argentine Pearlfish"
169 | print "38 Moon Jellyfish"
170 | print "39 Spotted Green Puffer"
171 | print "40 Sea Horse"
172 | print "41 Rainbow Rockfish"
173 | print "42 Archerfish"
174 | print "43 Jaw Characin"
175 | print "44 White Spotted Cichlid"
176 | print "45 Rainbow Fish"
177 | print "46 Leopard Bushfish"
178 | print "47 Redtail Catfish"
179 | print "48 Japanese Bullhead Shark"
180 | print "49 Lookdown Fish"
181 | print "\n"
182 | for i in range(amnt):
183 | sel = int(raw_input('Selection %s: ' % i))
184 | if sel < 40:
185 | header["fish_amount_%s" % i] = u8(1)
186 | header["fish_id_%s" % i] = u8(sel)
187 | header["fish_growth_level_%s" % i] = u16(0)
188 | header["fish_hungry_degree_%s" % i] = u16(0)
189 | header["fish_padding_%s" % i] = u16(1)
190 | header["fish_birthday_%s" % i] = u32(1) # Birthday is day 1
191 | header["current_day_%s" % i] = u32(1) # Current day set to 1
192 | else:
193 | print "Error: Invalid selection"
194 | else:
195 | print "Error: Invalid amount, skipping"
196 | amnt = 0
197 |
198 | header["fish_tables"] = pad(240 - (amnt * 16))
199 | header["object_tables"] = pad(160) # TODO: Add in object tables
200 |
201 | print "Processing ..."
202 |
203 | f = io.BytesIO()
204 | for k, v in header.items(): f.write(v)
205 | f.flush()
206 | f.seek(0)
207 | copy = f.read()
208 | crc32 = format(binascii.crc32(copy) & 0xFFFFFFFF, '08x')
209 | f.close()
210 |
211 | file = open('a0014682.dat', 'wb') # Not sure how the name is generated yet
212 | file.write(binascii.unhexlify('08051400')) # Magic Value
213 | file.write(binascii.unhexlify(crc32)) # CRC32
214 | file.write(copy) # Rest of File
215 | file.flush()
216 | file.close()
217 |
218 | print "\n"
219 | print "Completed Successfully"
220 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # File-Maker
2 | [](http://www.gnu.org/licenses/agpl-3.0)
3 | [](https://discord.com/invite/b4Y7jfD)
4 |
5 | These scripts will create static data files for these Wii Channels:
6 |
7 | - Everybody Votes Channel
8 | - Forecast Channel
9 | - News Channel
10 | - Nintendo Channel
11 | - Check Mii Out Channel/Mii Contest Channel
12 |
13 | These files are downloaded on the Wii, and contain news, weather info, etc that the Channel(s) display, as well as influencing some games' environments.
14 |
15 | ## Services and Modules
16 |
17 | We use the following services for this project:
18 |
19 | - [Datadog](https://datadoghq.com/) for analytics.
20 | - For News Channel, [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/intro) to get location coordinates.
21 | - For Everybody Votes Channel and Check Mii Out Channel, [MySQL](https://www.mysql.com/) to hold votes and suggestions.
22 | - [Sentry](https://sentry.io/) for error logging.
23 | - Webhooks to log when a script has been ran.
24 |
25 | Some notable Python modules used in the project are:
26 |
27 | - [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/) for HTML parsing.
28 | - [feedparser](https://pypi.python.org/pypi/feedparser) to parse RSS feeds.
29 | - [newspaper](http://newspaper.readthedocs.io/en/latest/) for news article scraping.
30 | - [nlzss](https://github.com/DorkmasterFlek/python-nlzss) for LZ compressing the files.
31 | - [requests](http://docs.python-requests.org/en/master/) for various HTTP requests.
32 | - [rsa](https://pypi.python.org/pypi/rsa) to create an RSA signed SHA-1 (that the Wii verifies downloaded files with).
33 |
34 | [AccuWeather](https://accuweather.com/) is used as the weather source for the Forecast Channel. For a list of news sources we use for the News Channel, [refer to this webpage](https://rc24.xyz/services/news.html).
35 |
36 | All files are LZ10 compressed.
37 |
38 | If you want to know the format of the files used by the Channels, you can [look at our Kaitais](https://github.com/RiiConnect24/Kaitai-Files), check the wiki, or look at the code.
39 |
40 | ## Installing Requirements
41 |
42 | These scripts run on Python 3.
43 |
44 | Just run `pip install -r requirements.txt` in the root folder and it'll install. You might have to run as `sudo` due to permissions.
45 |
46 | It's required to have a `config.json` for each Channel in the `Channels` folder. Fill out `config.json.template` for the Channels you want to run this script for, and rename it to `config.json`.
47 |
48 | Run the scripts as modules, e.g. `python -m Channels.Forecast_Channel.forecast`.
49 |
--------------------------------------------------------------------------------
/Tools/Forecast/parse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import binascii
5 | import collections
6 | import os
7 | import pickle
8 | import sys
9 | import collections
10 |
11 | print "Forecast File Parser"
12 | print "By John Pansera (2017-2020) - www.rc24.xyz"
13 | print "Parsing ...\n"
14 |
15 | filename = "forecast.bin"
16 | amnt = None
17 | offset = None
18 | file = None
19 | names = collections.OrderedDict()
20 |
21 |
22 | def read_int(x):
23 | return int(binascii.hexlify(file.read(x)), 16)
24 |
25 |
26 | def coord_decode(value):
27 | value = int(value, 16)
28 | if value >= 0x8000:
29 | value -= 0x10000
30 | return value * 0.0054931640625
31 |
32 |
33 | def parse_offsets(dict, count, offset):
34 | file.seek(offset)
35 | for i in range(count):
36 | code = read_int(1)
37 | padding = read_int(3)
38 | offset = read_int(4)
39 | dict[i] = [code, offset]
40 |
41 | def parse_weather_offsets(dict, count, offset):
42 | file.seek(offset)
43 | for i in range(count):
44 | international_code_1 = binascii.hexlify(file.read(2))
45 | international_code_2 = binascii.hexlify(file.read(2))
46 | international_offset = read_int(4)
47 | japan_code_1 = binascii.hexlify(file.read(2))
48 | japan_code_2 = binascii.hexlify(file.read(2))
49 | japan_offset = read_int(4)
50 | dict[i] = [international_code_1.upper(), international_code_2.upper(), international_offset, japan_code_1.upper(), japan_code_2.upper(), japan_offset]
51 |
52 |
53 | def get_text():
54 | str = ""
55 | null_last = False
56 | while True:
57 | char = file.read(1)
58 | str += char
59 | if char == '\00':
60 | if not null_last:
61 | null_last = True
62 | else:
63 | null_last = False
64 | break
65 | else: null_last = False
66 | return str
67 |
68 |
69 | file = open(filename, "rb")
70 |
71 | # Print file header information
72 |
73 | version = read_int(4)
74 | print "Version: %s" % version
75 | file_size = read_int(4)
76 | print "File Size: %s" % file_size
77 | crc32 = binascii.hexlify(file.read(4))
78 | print "CRC32: %s" % crc32
79 | timestamp_1 = read_int(4)
80 | timestamp_2 = read_int(4)
81 | print "Valid until: %s (for %s minutes)" % (timestamp_1,int(timestamp_1-timestamp_2))
82 | print "Generated at: %s" % timestamp_2
83 | file.seek(20) # Country Code
84 | country_code = binascii.hexlify(file.read(1))
85 | print "Country Code: %s (%s)" % (country_code, str(int(country_code, 16)).zfill(3))
86 | file.seek(21) # Language Code
87 | language_code = read_int(4)
88 | print "Language Code: %s" % language_code
89 | file.seek(28) # Message Offset
90 | message_offset = read_int(4)
91 | if message_offset == 0: print "No Message in file"
92 | else: print "Message exists in file"
93 | file.seek(32) # Long Forecast Entry Number
94 | long_count = read_int(4)
95 | file.seek(36) # Long Forecast Table Offset
96 | long_offset = read_int(4)
97 | print "%s long forecast table entries @ %s" % (long_count, hex(long_offset))
98 | file.seek(40) # Short Forecast Entry Number
99 | short_count = read_int(4)
100 | file.seek(44) # Short Forecast Table Offset
101 | short_offset = read_int(4)
102 | print "%s short forecast table entries @ %s" % (short_count, hex(short_offset))
103 | file.seek(48) # Weather Condition Codes Entry Number
104 | weatherconditions_count = read_int(4)
105 | file.seek(52) # Weather Condition Codes Table Offset
106 | weatherconditions_offset = read_int(4)
107 | print "%s weather condition entries @ %s" % (weatherconditions_count, hex(weatherconditions_offset))
108 | file.seek(56) # UV Index Entry Number
109 | uvindex_count = read_int(4)
110 | file.seek(60) # UV Index Table Offset
111 | uvindex_offset = read_int(4)
112 | print "%s uv index entries @ %s" % (uvindex_count, hex(uvindex_offset))
113 | file.seek(64) # Laundry Index Entry Number
114 | laundry_count = read_int(4)
115 | file.seek(68) # Laundry Index Table Offset
116 | laundry_offset = read_int(4)
117 | print "%s laundry index entries @ %s" % (laundry_count, hex(laundry_offset))
118 | file.seek(72) # Pollen Count Entry Number
119 | pollen_count = read_int(4)
120 | file.seek(76) # Pollen Count Entry Offset
121 | pollen_offset = read_int(4)
122 | print "%s pollen index entries @ %s" % (pollen_count, hex(pollen_offset))
123 | file.seek(80) # Location Entry Number
124 | amnt = read_int(4)
125 | file.seek(84) # Location Table Offset
126 | offset = read_int(4)
127 | print "%s location entries @ %s" % (amnt, hex(offset))
128 |
129 | # Parse Entries
130 | uvindex = collections.OrderedDict()
131 | laundry = collections.OrderedDict()
132 | pollen = collections.OrderedDict()
133 | weather = collections.OrderedDict()
134 | parse_offsets(uvindex, uvindex_count, uvindex_offset)
135 | parse_offsets(laundry, laundry_count, laundry_offset)
136 | parse_offsets(pollen, pollen_count, pollen_offset)
137 | parse_weather_offsets(weather, weatherconditions_count / 2, weatherconditions_offset)
138 |
139 | file.seek(uvindex[0][1])
140 | print "\nUV Index Entries:"
141 | for i in range(uvindex_count):
142 | uvindex[i][1] = get_text()
143 | print " %s : %s" % (uvindex[i][0], uvindex[i][1].decode('utf-16be'))
144 |
145 | laundry_str = ""
146 | file.seek(laundry[0][1])
147 | for i in range(laundry_count):
148 | laundry[i][1] = get_text()
149 | laundry_str += str(laundry[i][0]) + " "
150 | print "\nLaundry Index Entries: " + laundry_str
151 |
152 | pollen_str = ""
153 | file.seek(pollen[0][1])
154 | for i in range(pollen_count):
155 | pollen[i][1] = get_text()
156 | pollen_str += str(pollen[i][0]) + " "
157 | print "\nPollen Index Entries: " + pollen_str
158 |
159 | print "\nWeather Conditions:"
160 | print "Code 1 / Code 2 (International) / Code 1 / Code 2 (Japan)\n"
161 | file.seek(weather[0][2])
162 | for i in range(weatherconditions_count / 2):
163 | weather[i][2] = get_text()
164 | weather[i][5] = get_text()
165 | print " %s/%s/%s/%s : %s" % (weather[i][0], weather[i][1], weather[i][3], weather[i][4], weather[i][2].decode('utf-16be').replace('\n', ' '))
166 |
167 | raw_input("\nParsing location entries:")
168 |
169 | # Parse location entries
170 | file.seek(offset)
171 | for i in range(amnt):
172 | loc_name = binascii.hexlify(file.read(4))
173 | city = read_int(4)
174 | region = read_int(4)
175 | country = read_int(4)
176 | lat = coord_decode(binascii.hexlify(file.read(2)))
177 | lng = coord_decode(binascii.hexlify(file.read(2)))
178 | zoom1 = read_int(1)
179 | zoom2 = read_int(1)
180 | file.seek(2, 1)
181 | names[i] = [loc_name, city, region, country, lat, lng, zoom1, zoom2]
182 |
183 |
184 | file.seek(names[0][1])
185 | for k in names.keys():
186 | print "Location ID: %s" % names[k][0].upper()
187 | names[k][1] = get_text()
188 | print "City: %s" % names[k][1].decode('utf-16be').encode('utf-8')
189 | if names[k][2] != 0:
190 | names[k][2] = get_text()
191 | print "Region: %s" % names[k][2].decode('utf-16be').encode('utf-8')
192 | else: print "No Region"
193 | if names[k][3] != 0:
194 | names[k][3] = get_text()
195 | print "Country: %s" % names[k][3].decode('utf-16be').encode('utf-8')
196 | else: print "No Country"
197 | print "Latitude Coordinate: %s" % names[k][4]
198 | print "Longitude Coordinate: %s" % names[k][5]
199 | print "Zoom 1: %s" % names[k][6]
200 | print "Zoom 2: %s" % names[k][7]
201 |
202 |
203 | print "\nDumping Locations Database ..."
204 | with open('locations.db', 'wb') as file:
205 | pickle.dump(names, file)
206 |
207 | print "Completed Sucessfully"
208 |
--------------------------------------------------------------------------------
/Tools/LZ Tools/DSDecmp/DSDecmp.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/DSDecmp/DSDecmp.exe
--------------------------------------------------------------------------------
/Tools/LZ Tools/DSDecmp/Plugins/GoldenSunDD.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/DSDecmp/Plugins/GoldenSunDD.dll
--------------------------------------------------------------------------------
/Tools/LZ Tools/DSDecmp/Plugins/LuminousArc.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/DSDecmp/Plugins/LuminousArc.dll
--------------------------------------------------------------------------------
/Tools/LZ Tools/lzss/lzss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/lzss/lzss
--------------------------------------------------------------------------------
/Tools/LZ Tools/lzss/lzss-mac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/lzss/lzss-mac
--------------------------------------------------------------------------------
/Tools/LZ Tools/lzss/lzss.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RiiConnect24/File-Maker/fe41a6ac4bddd1fd1e67030fb646cc200826fad9/Tools/LZ Tools/lzss/lzss.exe
--------------------------------------------------------------------------------
/Tools/Sign_Encrypt/sign_encrypt.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import binascii
3 | import collections
4 | import os
5 | import pyaes
6 | import rsa
7 | import struct
8 | import subprocess
9 |
10 |
11 | def u8(data):
12 | return struct.pack(">B", data)
13 |
14 |
15 | def u16(data):
16 | return struct.pack(">H", data)
17 |
18 |
19 | def u32(data):
20 | return struct.pack(">I", data)
21 |
22 | parser = argparse.ArgumentParser(description="Sign / Encrypt WiiConnect24 files.")
23 | parser.add_argument("-t", "--type",
24 | type=str, nargs="+",
25 | help="Type of file. Set either enc for encrypted file, or dec for decrypted (compressed) file.")
26 | parser.add_argument("-in", "--input",
27 | type=str, nargs="+",
28 | help="Input file.")
29 | parser.add_argument("-out", "--output",
30 | type=str, nargs="+",
31 | help="Output file.")
32 | parser.add_argument("-c", "--compress",
33 | type=str, nargs="+",
34 | help="If set, this will compress the file before signing.")
35 | parser.add_argument("-key", "--aes-key",
36 | type=str, nargs="+",
37 | help="AES key in hex or a path.")
38 | parser.add_argument("-iv", "--iv-key",
39 | type=str, nargs="+",
40 | help="AES IV in hex or a path.")
41 | parser.add_argument("-rsa", "--rsa-key-path",
42 | type=str, nargs="+",
43 | help="RSA private key path. If not specified, it will use the private key in Private.pem if it exists.")
44 |
45 | args = parser.parse_args()
46 |
47 | if args.compress is not None:
48 | subprocess.call(["cp", args.input[0], "temp"])
49 | subprocess.call(["lzss", "-evf", "temp"])
50 |
51 | if args.type[0] == "enc":
52 | filename = args.input[0]
53 | elif args.type[0] == "dec":
54 | filename = "temp"
55 |
56 | with open(filename, "rb") as f:
57 | data = f.read()
58 |
59 | """RSA sign the file."""
60 |
61 | if args.rsa_key_path is not None:
62 | rsa_key_path = args.rsa_key_path[0]
63 | else:
64 | rsa_key_path = "Private.pem"
65 |
66 | with open(rsa_key_path, "rb") as source_file:
67 | private_key_data = source_file.read()
68 |
69 | private_key = rsa.PrivateKey.load_pkcs1(private_key_data, "PEM")
70 |
71 | signature = rsa.sign(data, private_key, "SHA-1")
72 |
73 | if args.type[0] == "enc":
74 | if args.iv_key is not None:
75 | try:
76 | iv = binascii.unhexlify(args.iv_key[0])
77 | except:
78 | iv = open(args.iv_key[0], "rb").read()
79 | else:
80 | iv = os.urandom(16)
81 |
82 | try:
83 | key = binascii.unhexlify(args.aes_key[0])
84 | except:
85 | key = open(args.aes_key[0], "rb").read()
86 |
87 | aes = pyaes.AESModeOfOperationOFB(key, iv=iv)
88 | processed = aes.encrypt(data)
89 | elif args.type[0] == "dec":
90 | processed = data
91 |
92 | content = collections.OrderedDict()
93 |
94 | content["magic"] = b"WC24" if args.type[0] == "enc" else u32(0)
95 | content["version"] = u32(1) if args.type[0] == "enc" else u32(0)
96 | content["filler"] = u32(0)
97 | content["crypt_type"] = u8(1) if args.type[0] == "enc" else u8(0)
98 | content["pad"] = u8(0) * 3
99 | content["reserved"] = u8(0) * 32
100 | content["iv"] = iv if args.type[0] == "enc" else u8(0) * 16
101 | content["signature"] = signature
102 | content["data"] = processed
103 |
104 | if os.path.exists(args.output[0]):
105 | os.remove(args.output[0])
106 |
107 | if args.type[0] == "dec":
108 | os.remove("temp")
109 |
110 | for values in content.values():
111 | with open(args.output[0], "ab+") as f:
112 | f.write(values)
113 |
114 | print("Completed Successfully")
115 |
--------------------------------------------------------------------------------
/Tools/Time/convert.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import sys
3 |
4 |
5 | def main():
6 | wii_to_timestamp = (int(sys.argv[1], 16) * 60) + 946684800
7 | date = datetime.datetime.utcfromtimestamp(wii_to_timestamp).strftime('%Y-%m-%d %H:%M')
8 | print "Timestamp: %s" % wii_to_timestamp
9 | print "Date: %s" % date
10 |
11 |
12 | if __name__ == "__main__":
13 | main()
14 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | bs4
2 | cloudflare
3 | datadog
4 | feedparser
5 | ftfy==4.4.3
6 | googlemaps
7 | lxml
8 | newspaper3k
9 | nlzss
10 | pyaes
11 | requests
12 | rsa
13 | sentry_sdk
14 | unidecode
15 | pillow
16 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import errno
2 | import logging
3 | import os
4 | import requests
5 | import sentry_sdk
6 | import struct
7 | from sentry_sdk.integrations.logging import LoggingIntegration
8 |
9 | """Unification of utilities used by all scripts."""
10 |
11 | requests.packages.urllib3.disable_warnings() # This is so we don't get some warning about SSL.
12 |
13 | production = False
14 | p_errors = False
15 |
16 | def setup_log(sentry_url, print_errors):
17 | global logger, production
18 | sentry_logging = LoggingIntegration(
19 | level=logging.INFO,
20 | event_level=logging.INFO
21 | )
22 | sentry_sdk.init(dsn=sentry_url, integrations=[sentry_logging])
23 | logger = logging.getLogger(__name__)
24 | p_errors = print_errors
25 | production = True
26 |
27 | def log(msg, level): # TODO: Use number levels, strings are annoying
28 | if p_errors:
29 | print(msg)
30 |
31 | if production:
32 | if level == "VERBOSE":
33 | logger.debug(msg)
34 | elif level == "INFO":
35 | logger.info(msg)
36 | elif level == "WARNING":
37 | logger.warning(msg)
38 | elif level == "CRITICAL":
39 | logger.critical(msg)
40 |
41 | def mkdir_p(path):
42 | try:
43 | os.makedirs(path)
44 | except:
45 | pass
46 |
47 |
48 | """Pack integers to specific type."""
49 |
50 | # Unsigned integers
51 |
52 | def u8(data):
53 | if not 0 <= data <= 255:
54 | log("u8 out of range: %s" % data, "INFO")
55 | data = 0
56 | return struct.pack(">B", data)
57 |
58 |
59 | def u16(data):
60 | if not 0 <= data <= 65535:
61 | log("u16 out of range: %s" % data, "INFO")
62 | data = 0
63 | return struct.pack(">H", data)
64 |
65 |
66 | def u32(data):
67 | if not 0 <= data <= 4294967295:
68 | log("u32 out of range: %s" % data, "INFO")
69 | data = 0
70 | return struct.pack(">I", data)
71 |
72 |
73 | def u32_littleendian(data):
74 | if not 0 <= data <= 4294967295:
75 | log("u32 little endian out of range: %s" % data, "INFO")
76 | data = 0
77 | return struct.pack("b", data)
86 |
87 |
88 | def s16(data):
89 | if not -32768 <= data <= 32767:
90 | log("s16 out of range: %s" % data, "INFO")
91 | data = 0
92 | return struct.pack(">h", data)
93 |
94 |
95 | def s32(data):
96 | if not -2147483648 <= data <= 2147483647:
97 | log("s32 out of range: %s" % data, "INFO")
98 | data = 0
99 | return struct.pack(">i", data)
100 |
--------------------------------------------------------------------------------