-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.js
216 lines (214 loc) · 8.77 KB
/
main.js
1
2
3
4
5
6
7
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
53
54
55
56
57
58
59
60
61
62
63
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/**
* Coded by Wayveyx
* Development started: 8/29/22 at 8:38 PM CST
* 2.1 to Legacy level converter
* Made for 1.6 GDPS
* Credits to zmx for helping me understand the level header, and for providing objCharts conversions.
*/
const config = require("./conf.json")
const level = require("./level")
const { objChart, colObjs } = require("./objCharts")
const axios = require("axios")
const fs = require("fs")
const path = require("path")
const zlib = require("zlib")
const base64 = require("base-64")
const isb64 = require("is-base64")
const yargs = require("yargs")
const { boolean } = require("yargs")
const argv = yargs
.option('list', {
alias: 'l',
description: 'List specific details.', //Does pretty much nothing currently
type: 'boolean'
})
.option('dry', {
alias: 'd',
description: 'Don\'t save or upload level.',
type: 'boolean'
})
.option('file', {
alias: 'f',
description: 'Create file, but dont upload level.',
type: 'boolean'
})
.option('target', {
alias: 't',
description: 'Change target version.',
type: 'string',
default: config.target
})
.option('unlisted', {
alias: 'h',
description: "Reupload level as unlisted.",
type: 'boolean'
})
.option('url', {
alias: 'u',
description: 'Change target download server.',
type: 'string',
default: config.download
})
.option('upload', {
alias: 's',
description: 'Change target upload server.',
type: 'string',
default: config.upload
})
.help()
.version("0.4.3")
.alias('help', 'h')
.alias('upload', 'server')
.argv;
var dl = argv.url;
var parse;
var dirPath = path.join(__dirname);
fs.readdir(dirPath, async function (err, files) { //Planning on rewriting this
if(err) throw err;
let levels = [];
for(i = 0; i < files.length; i++) {
let fileinfo = files[i];
if(argv._.join("")) fileinfo = files[i].includes(argv._.join(""));
if(fileinfo) {
levels.push(files[i]);
}
}
if(levels.length < 1) {
console.log(`Level file not found. Attempting to download from ${dl}...`);
await axios.post(`${dl}downloadGJLevel22.php`, `levelID=${argv._}&gameVersion=21&secret=Wmfd2893gb7`, { headers: { 'User-Agent': '' } })
.then(function (res) {
if(res.data == "-1") return console.log("Level not found. (" + res.data + ")");
else {
let levelString = Buffer.from(res.data.split(":")[7], 'base64')
new Promise(function(resolve, reject) {
let unencrypted;
let levelData;
if(res.data.split(":")[7].startsWith("kS")) unencrypted = res.data.split(":")[7].startsWith("kS")
if(unencrypted) {
levelData = unencrypted
resolve(levelData)
} else {
zlib.unzip(levelString, (err, buffer) => {
if(err) reject(console.error(err));
levelData = buffer.toString()
resolve(parseLevel(res.data, levelData, "web"))
})
}
});
}
});
} else if(levels.length > 1) return console.log("Please only provide one level file. Found:\n" + levels.join("\n"))
else {
let levelData = fs.readFileSync(levels[0], 'utf-8')
if(levels[0].split(".")[1] == "gmd") { //Better way to determine file extension
levelData = level.plist(levelData)
parseLevel(argv._, levelData, "gdshare")
}
else parseLevel(argv._, levelData)
}
});
async function parseLevel(string, data, src) { //change to src - v0.4.0
let levelInfo;
switch(src) {
case "web":
//console.log(data)
parse = new level(data)
levelInfo = level.robArray(string, ":")
parse.name = levelInfo[2]
parse.desc = base64.decode(levelInfo[3], 'base64')
parse.length = levelInfo[15]
parse.track = levelInfo[12]
console.log(`${parse.name} downloaded.`)
break;
case "gdshare": //Readablility suffers but consistency improves.
levelInfo = Buffer.from(data.k4, 'base64');
await new Promise(function(resolve, reject) {
zlib.unzip(levelInfo, (err, buffer) => {
if(err) reject(console.error(err));
levelInfo = buffer.toString()
resolve(levelInfo)
})
})
parse = new level(levelInfo)
parse.name = data.k2
parse.desc = base64.decode(data.k3, '')
parse.length = 0 //GDShare doesn't support this
parse.track = data.k8 ? data.k8 : 0; //Only supporting 'normal' songs, <3 cyni
break;
default:
parse = new level(data)
parse.name = data;
break;
}
convObjs()
}
let maxObjs = level.perVersion(argv.target).max;
let gameVersion = level.perVersion(argv.target).gameVersion;
let songs = level.perVersion(argv.target).songs
let illegals = ['14', '31', '34', '37', '38', '42', '43', '44', '55', '63', '64', '79', '100', '102', '108', '109', '112', '142', '189'] //this will probably change lol
let colorObjs = ['29', '30', '104', '105']
let acceptedValues = ['1', '2', '3', '4', '5', '6']
let colValues = ['7', '8', '9', '10', '11', '14']
let illegalObjs = new Array();
function convObjs() { //2.1 -> legacy object time
let objects = parse.objects
if(parse.track > songs) parse.track = 0; //Stereo Madness for invalid song
let i = 0;
let newObj = "";
new Promise(function(resolve, reject) {
objects.forEach(object => {
if(object.indexOf(",") == -1) reject(object = ""); //Remove completely illegal objects
let objInfo = level.robArray(object)
let tempObj = "";
if(objInfo[1] === '899') { //Color trigger conversion
let colorType = objInfo[23]
if(colObjs[colorType]) objInfo[1] = colObjs[colorType]
}
if(objInfo[1] > maxObjs && objChart[objInfo[1]] !== undefined) objInfo[1] = objChart[objInfo[1]]
if(illegals.indexOf(objInfo[1]) !== -1 || objInfo[1] > maxObjs) illegalObjs.push(objInfo[1]), object = ""; //Remove bad guys
if(object.indexOf(",") !== -1) {
for (const [key, value] of Object.entries(objInfo)) {
if(acceptedValues.includes(key) || (colorObjs.includes(`${objInfo[1]}`) && colValues.includes(key))) {
if(key > 1) newObj += ",", tempObj += ",";
newObj += `${key},${value}`;
tempObj += `${key},${value}`;
}
}
newObj += ";";
tempObj = ""; //used for debugging
}
i++;
if(i == objects.length) {
if(illegalObjs.length > 0) {
if(argv.list) console.log(illegalObjs)
else console.log(`${illegalObjs.length} illegal objects removed.`)
}
if(!argv.dry) resolve(writeFile(newObj))
else resolve(console.log("Level converted, though not saved."))
}
});
});
}
function writeFile(string) {
let header = level.header(parse.header, argv.target)
let levelString = `${header};${string}`
let unlisted = 0;
if(!argv.file) {
console.log("Level Converted. Uploading..")
new Promise(function(resolve, reject) {
if(config.udid == "" || config.udid == undefined) return console.log("Upload Failed! UDID required. Please contact your server administrator.")
if(argv.unlisted) unlisted = 1;
if(isb64(parse.desc)) parse.desc = base64.decode(parse.desc) //Secondary check for gmd files.
axios.post(`${argv.upload}uploadGJLevel.php`, `udid=${config.udid}&userName=${config.username}&levelID=0&levelName=${parse.name}&levelDesc=${parse.desc}&levelVersion=1&levelLength=${parse.length}&audioTrack=${parse.track}&gameVersion=${gameVersion}&secret=${config.secret}&unlisted=${unlisted}&levelString=${levelString}&levelReplay=0`, { headers: { 'User-Agent': '' } })
.then(function (res) {
if(res.data == "-1") return console.log("Upload Failed.")
console.log(res.data)
})
})
} else {
fs.writeFile(`./${argv._}-conv.txt`, levelString, 'utf-8', (err) => {
if(err) console.log("Something went wrong...")
console.log(`Level converted. Saved as: ${argv._}-conv`);
})
}
}