Comparing version 1.0.4 to 1.0.5
214
index.js
@@ -1,3 +0,4 @@ | ||
const crc32 = require("buffer-crc32"); | ||
const jimp = require("jimp"); | ||
const Promise = require("bluebird"); | ||
const crc32 = require("buffer-crc32"); | ||
const jimp = require("jimp"); | ||
@@ -12,2 +13,14 @@ const fs = require("fs"); | ||
const COURSE_SAVE_NAME_OFFSET = 0x28; | ||
const COURSE_SAVE_NAME_LENGTH = 0x42; | ||
const COURSE_SAVE_MAKER_OFFSET = 0x92; | ||
const COURSE_SAVE_MAKER_LENGTH = 0x14; | ||
const COURSE_SAVE_TYPE_OFFSET = 0x6A; | ||
const COURSE_SAVE_TYPE_READABLE = { | ||
"M1": "Super Mario Bros", | ||
"M3": "Super Mario Bros 3", | ||
"MW": "Super Mario World", | ||
"WU": "New Super Mario Bros U" | ||
}; | ||
const TNL_SIZE = 0xC800; | ||
@@ -33,2 +46,6 @@ const TNL_JPEG_MAX_SIZE = 0xC7F8; | ||
pathToSave = path.resolve(pathToSave); | ||
if (!fs.existsSync(pathToSave)) throw new Error(`No such folder exists:\n${pathToSave}`); | ||
fs.access(pathToSave, fs.constants.R_OK | fs.constants.W_OK, (err) => { | ||
if (err) throw new Error("Please close you emuulator before executing your script"); | ||
}); | ||
fs.readFile(path.resolve(`${pathToSave}/save.dat`), (err, data) => { | ||
@@ -48,2 +65,3 @@ if (err) throw err; | ||
this.data = data; | ||
this.courses = {}; | ||
} | ||
@@ -161,3 +179,3 @@ | ||
let tnl = new Tnl(coursePath + "/thumbnail0.tnl"); | ||
let jpeg = await tnl.toJpeg(true); | ||
let jpeg = await tnl.toJpeg(); | ||
fs.writeFile(coursePath + "/thumbnail0.jpg", jpeg, null, () => { | ||
@@ -173,3 +191,3 @@ resolve(); | ||
let tnl = new Tnl(coursePath + "/thumbnail1.tnl"); | ||
let jpeg = await tnl.toJpeg(false); | ||
let jpeg = await tnl.toJpeg(); | ||
fs.writeFile(coursePath + "/thumbnail1.jpg", jpeg, null, () => { | ||
@@ -189,2 +207,105 @@ resolve(); | ||
}, | ||
importJpeg: async function () { | ||
let promises = []; | ||
for (let i = 0; i < SAVE_ORDER_SIZE; i++) { | ||
let coursePath = path.resolve(`${this.pathToSave}/course${i.pad(3)}/`); | ||
promises.push(new Promise(async (resolve) => { | ||
let exists = false; | ||
await new Promise((resolve) => { | ||
fs.access(coursePath, fs.constants.R_OK | fs.constants.W_OK, (err) => { | ||
exists = !err; | ||
resolve(); | ||
}); | ||
}); | ||
if (exists) { | ||
await Promise.all([ | ||
new Promise(async (resolve) => { | ||
try { | ||
let jpeg = new Tnl(coursePath + "/thumbnail0.jpg"); | ||
let tnl = await jpeg.fromJpeg(true); | ||
fs.writeFile(coursePath + "/thumbnail0.tnl", tnl, null, () => { | ||
resolve(); | ||
}) | ||
} catch (err) { | ||
resolve(); | ||
} | ||
}), | ||
new Promise(async (resolve) => { | ||
try { | ||
let jpeg = new Tnl(coursePath + "/thumbnail1.jpg"); | ||
let tnl = await jpeg.fromJpeg(false); | ||
fs.writeFile(coursePath + "/thumbnail1.tnl", tnl, null, () => { | ||
resolve(); | ||
}); | ||
} catch (err) { | ||
resolve(); | ||
} | ||
}) | ||
]); | ||
} | ||
resolve(); | ||
})); | ||
} | ||
await Promise.all(promises); | ||
}, | ||
loadCourses: async function () { | ||
let promises = []; | ||
for (let i = 0; i < SAVE_ORDER_SIZE; i++) { | ||
let course = `course${i.pad(3)}`; | ||
let coursePath = path.resolve(`${this.pathToSave}/${course}/`); | ||
promises.push(new Promise(async (resolve) => { | ||
let exists = false; | ||
await new Promise((resolve) => { | ||
fs.access(coursePath, fs.constants.R_OK | fs.constants.W_OK, (err) => { | ||
exists = !err; | ||
resolve(); | ||
}); | ||
}); | ||
if (exists) { | ||
fs.readFile(path.resolve(`${coursePath}/course_data.cdt`), (err, data) => { | ||
if (err) throw err; | ||
let titleBuf = data.slice(COURSE_SAVE_NAME_OFFSET, COURSE_SAVE_NAME_OFFSET + COURSE_SAVE_NAME_LENGTH); | ||
let title = ""; | ||
for (let i = 0; i < COURSE_SAVE_NAME_LENGTH; i++) { | ||
let charBuf = titleBuf.slice(i, i+1); | ||
if (charBuf.readUInt8(0) !== 0) { | ||
title += charBuf.toString(); | ||
} | ||
} | ||
let makerBuf = data.slice(COURSE_SAVE_MAKER_OFFSET, COURSE_SAVE_MAKER_OFFSET + COURSE_SAVE_MAKER_LENGTH); | ||
let maker = ""; | ||
let doBreak = false; | ||
for (let i = 0; i < COURSE_SAVE_MAKER_LENGTH; i++) { | ||
let charBuf = makerBuf.slice(i, i+1); | ||
if (charBuf.readUInt8(0) === 0) { | ||
if (doBreak) break; | ||
doBreak = true; | ||
} else { | ||
doBreak = false; | ||
maker += charBuf.toString(); | ||
} | ||
} | ||
let type = data.slice(COURSE_SAVE_TYPE_OFFSET, COURSE_SAVE_TYPE_OFFSET + 2).toString(); | ||
this.courses[course] = { | ||
title: title, | ||
maker: maker, | ||
type: type, | ||
type_readable: COURSE_SAVE_TYPE_READABLE[type] | ||
}; | ||
resolve(); | ||
}); | ||
} else { | ||
resolve(); | ||
} | ||
})); | ||
} | ||
await Promise.all(promises); | ||
return this.courses; | ||
} | ||
@@ -218,43 +339,49 @@ | ||
// image pre-processing | ||
let sizeOK = false; | ||
let data = await new Promise((resolve) => { | ||
fs.readFile(this.pathToFile, (err, data) => { | ||
if (err) throw err; | ||
resolve(data); | ||
}); | ||
}); | ||
if (data.length <= TNL_JPEG_MAX_SIZE) { | ||
sizeOK = true; | ||
} | ||
let image = await jimp.read(this.pathToFile); | ||
let skipPreprocessing = false; | ||
if (sizeOK && (image.bitmap.width === TNL_DIMENSION[0][0] && image.bitmap.height === TNL_DIMENSION[0][1] || | ||
image.bitmap.width === TNL_DIMENSION[1][0] && image.bitmap.height === TNL_DIMENSION[1][1])) { | ||
skipPreprocessing = true; | ||
} | ||
if (isWide === null) { | ||
let aspectRatio = image.bitmap.width / image.bitmap.height; | ||
if (aspectRatio > TNL_ASPECT_RATIO[0] - TNL_ASPECT_RATIO_THRESHOLD[0] && aspectRatio < TNL_ASPECT_RATIO[0] + TNL_ASPECT_RATIO_THRESHOLD[0]) { | ||
isWide = true; | ||
} else if (aspectRatio > TNL_ASPECT_RATIO[1] - TNL_ASPECT_RATIO_THRESHOLD[1] && aspectRatio < TNL_ASPECT_RATIO[1] + TNL_ASPECT_RATIO_THRESHOLD[1]) { | ||
isWide = false; | ||
} | ||
// image pre-processing | ||
if (!skipPreprocessing) { | ||
if (isWide === null) { | ||
isWide = TNL_ASPECT_RATIO[0] - TNL_ASPECT_RATIO_THRESHOLD[0] - aspectRatio <= TNL_ASPECT_RATIO[1] + TNL_ASPECT_RATIO_THRESHOLD[1] + aspectRatio; | ||
let aspectRatio = image.bitmap.width / image.bitmap.height; | ||
if (aspectRatio > TNL_ASPECT_RATIO[0] - TNL_ASPECT_RATIO_THRESHOLD[0] && aspectRatio < TNL_ASPECT_RATIO[0] + TNL_ASPECT_RATIO_THRESHOLD[0]) { | ||
isWide = true; | ||
} else if (aspectRatio > TNL_ASPECT_RATIO[1] - TNL_ASPECT_RATIO_THRESHOLD[1] && aspectRatio < TNL_ASPECT_RATIO[1] + TNL_ASPECT_RATIO_THRESHOLD[1]) { | ||
isWide = false; | ||
} | ||
if (isWide === null) { | ||
isWide = TNL_ASPECT_RATIO[0] - TNL_ASPECT_RATIO_THRESHOLD[0] - aspectRatio <= TNL_ASPECT_RATIO[1] + TNL_ASPECT_RATIO_THRESHOLD[1] + aspectRatio; | ||
} | ||
} | ||
} | ||
if (isWide) { | ||
if (doClip) { | ||
image.cover(TNL_DIMENSION[0][0], TNL_DIMENSION[0][1]); | ||
if (isWide) { | ||
if (doClip) { | ||
image.cover(TNL_DIMENSION[0][0], TNL_DIMENSION[0][1]); | ||
} else { | ||
image.contain(TNL_DIMENSION[0][0], TNL_DIMENSION[0][1]); | ||
} | ||
} else { | ||
image.contain(TNL_DIMENSION[0][0], TNL_DIMENSION[0][1]); | ||
if (doClip) { | ||
image.cover(TNL_DIMENSION[1][0], TNL_DIMENSION[1][1]); | ||
} else { | ||
image.contain(TNL_DIMENSION[1][0], TNL_DIMENSION[1][1]); | ||
} | ||
} | ||
} else { | ||
if (doClip) { | ||
image.cover(TNL_DIMENSION[1][0], TNL_DIMENSION[1][1]); | ||
} else { | ||
image.contain(TNL_DIMENSION[1][0], TNL_DIMENSION[1][1]); | ||
} | ||
} | ||
// wrap tnl data around jpeg | ||
let data = await new Promise((resolve) => { | ||
image.getBuffer(jimp.MIME_JPEG, (err, buffer) => { resolve(buffer); }); | ||
}); | ||
// lower quality until it fits | ||
let quality = 100; | ||
while (data.length > TNL_JPEG_MAX_SIZE) { | ||
quality -= 5; | ||
if (quality < 0) { | ||
reject("File could not be transformed into jpeg with lowest quality setting."); | ||
} | ||
let quality = 80; | ||
data = await new Promise((resolve) => { | ||
@@ -264,4 +391,17 @@ image.quality(quality); | ||
}); | ||
// lower quality until it fits | ||
while (data.length > TNL_JPEG_MAX_SIZE) { | ||
quality -= 5; | ||
if (quality < 0) { | ||
reject("File could not be transformed into jpeg with lowest quality setting."); | ||
} | ||
data = await new Promise((resolve) => { | ||
image.quality(quality); | ||
image.getBuffer(jimp.MIME_JPEG, (err, buffer) => { resolve(buffer); }); | ||
}); | ||
} | ||
} | ||
// wrap tnl data around jpeg | ||
let length = Buffer.alloc(4); | ||
@@ -268,0 +408,0 @@ length.writeUInt32BE(data.length, 0); |
{ | ||
"name": "cemu-smm", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "This is a module to simplify all kinds of tasks with Loadiine Super Mario Maker save files and respectively Cemu", | ||
@@ -12,2 +12,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"bluebird": "^3.5.0", | ||
"buffer-crc32": "latest", | ||
@@ -14,0 +15,0 @@ "jimp": "^0.2.27" |
@@ -19,3 +19,3 @@ # cemu-smm | ||
let fs = require("fs"); | ||
(async () => { | ||
@@ -33,2 +33,5 @@ // let us load our SMM save file | ||
// import all jpg files and create tnl files in their respective course folder | ||
save.importJpeg(); | ||
// extract all tnl files to jpeg in their respective course folder | ||
@@ -88,3 +91,3 @@ save.exportJpeg(); | ||
```js | ||
let jpeg = await jpeg.fromJpeg([isWide, [doCrop = false]]); | ||
let jpeg = await tnl.toJpeg(); | ||
``` | ||
@@ -129,2 +132,12 @@ Save your file | ||
### JPEG mass import | ||
To convert all jpeg files inside your save to tnl, call | ||
```js | ||
save.importJpeg(); | ||
``` | ||
Files inside course folders must be named ```thumbnail0.jpg``` or ```thumbnail1.jpg````. | ||
Refer to [this](https://github.com/Tarnadas/cemu-smm/blob/master//tutorial/import_thumbnail.md) tutorial. | ||
## License | ||
@@ -131,0 +144,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23130
370
149
3
+ Addedbluebird@^3.5.0
+ Addedbluebird@3.7.2(transitive)