webui stuff

This commit is contained in:
vvo
2023-11-15 22:41:01 +08:00
parent 206212be2f
commit df4a678946
36 changed files with 2913 additions and 143 deletions
+37 -60
View File
@@ -1,26 +1,52 @@
# SOUND VOLTEX
**Plugin Version:** fork-6.0.2.3
**Plugin Version:** fork-6.0.2.4
**Supported game versions:** EXCEED GEAR
**Latest supported game datacode:** 2023102400
**Supported game versions:** EXCEED GEAR (2023102400)
**Unsupported game versions:** Every other game, as they're untested.
**Required Asphyxia Core version** [1.50c](https://github.com/asphyxia-core/asphyxia-core.github.io/releases/tag/v1.50)
**Notes:**
- This fork enables a few of the features that are added in the later updates of Exceed Gear. However, I still highly recommend using the official stable version of the plugin.
- Before using this plugin:
- run the [WebUI Asset Update](/plugin/sdvx@asphyxia/update%20webui%20assets). Do this every data update.
- go to [Unlocking Events](/plugin/sdvx@asphyxia/unlocking%20events) and click "Apply" at least once. Check out the many options available while you're there.
- also adjust the plugin settings at the WebUI dashboard to your liking.
- Don't expect stability, there might be some bugs. Please back up your save data to prevent unwanted issues (just in case)
- Please be aware that this plugin and the savefile it produces are incompatible with the official plugin, or any other forks. Please be cautious of using other plugins' savefiles with this fork of the plugin, or using this fork's savefile with other plugins.
- Before using this plugin, run the [WebUI Asset Update](/plugin/sdvx@asphyxia/update%20webui%20assets). Do this every data and plugin update.
- Please back up your save data to prevent unwanted issues (just in case)
Changelog
===========
## fork-6.0.2.4
### New:
1. Main:
- Updated EG course data (isNew flags)
2. WebUI:
- Profile details
- Added display of skill frames (normal or god.) Asset update required.
- frame might misalign a bit depending on window size, sorry
- Added display of arena rank image. Asset update required.
- Added display of your currently selected skill title.
- Changed how Megamix rate and Ultimate rate is displayed in arena stats.
- Asset update
- Added ability to extract images/textures from .ifs files, with file information provided (seen on data/webui.ts.) Used to extract arena/skill course-related textures. Will probably utilize more in the future. Only ifs files used in latest data are supported.
- Massive thanks to [mon's ifstools](https://github.com/mon/ifstools), the code where this feature is largely based on.
- Added [pngjs library files](https://github.com/pngjs/pngjs) to handle PNG file creation. Couldn't find a more elegant way but this will do for now.
- Added course_data.json update using the course data in exg.ts.
3. Other:
- Added bgm_convert.sh file for Linux
- Updated bgm_convert.bat
### Fixes:
1. WebUI
- Fixed Japan-exclusive warning not displaying for some Premium Generator sets.
### Todo:
1. Figure out how to use image (png) files to appear in information/news popup.
2. More work on online matchmaking (globalMatch.) I can't seem to make clients establish a connection.
## fork-6.0.2.3
### New:
@@ -37,7 +63,7 @@ Changelog
- (Update webui resources) Added code to extract wma audio from .s3p files. Largely based on [mon's s3p_extract](https://github.com/mon/s3p_extract) code.
3. Others:
- DEMOLOOP_INFORMATION implementation (attract mode announcements images)
- Updated bgm_convert batch script to remove the need for s3p_extract.
- Updated bgm\_convert batch script to remove the need for s3p\_extract.
### Fixes:
@@ -50,12 +76,6 @@ Changelog
- Updated BPL Triple Tribe event category from 'gift' to 'event_online.'
- Added missing matchmaker model file.
### Todo:
1. Figure out how to extract textures from .ifs files in TS/JS.
2. ~~Figure out how to extract audio files from .s3p files in TS/JS~~ Figure out how to convert wma audio to mp3 in JS/TS.
3. Figure out how to use image (png) files to appear in information/news popup.
4. More work/research on online matchmaking (globalMatch.) I can't seem to make clients establish a connection.
## fork-6.0.2.2
@@ -84,46 +104,3 @@ Changelog
1. Fixes WebUI resource update not adding video submonitor backgrounds. I 100% recommend updating your WebUI assets.
## fork-6.0.2.1
### Fixes:
1. Fixed missing Megamix songs (extend string param getting cut off)
## fork-6.0.2.0
### New:
1. Support for EG 2023091200
2. Added Arena Station 8, 9 and 10.
3. Added Valkyrie Generator Vol. 12
4. Added Premium Generator
- Re:Zero Set 1
- Re:Zero Set 2
- Vol. 3
5. Enabled HEXADIVER 8 and 9
6. Added option for BPL Team Supporter banner
7. Added support for System Backgrounds
8. Added event data:
- BPL S3 Triple Tribe songs unlock toggle (suspicions, etc.)
- KAC 2023 song unlock toggle (パーフェクトイーター)
- KAC 2023 song stamp event (for 累乗のカルマ)
- KAC 2023 song stamp event (for Stylus & QQ)
9. Updated licensed songs and valk-exclusive songs list
10. Skill Analyzer God Mode implementation.
- Special courses (Red Bull courses, etc.) do not have God Mode courses.
11. Added and fixed Skill Analyzer course data:
- Sets 6 and 7
- BMK2021 courses
- Spring 2023 courses
- KAC 2023 Qualifier course sets 1 & 2
12. Megamix Battle song list update
### Fixes:
1. Reduced songNum value from 3000 to 2100. Potential fix for some users with "unlock all songs" enabled who are experiencing request timeouts resulting in missing features and songs.
2. Fixed bug in stamp select data.
3. Fixed bug for when common.ts or profiles.ts is looking for a specific event key but it is undefined.
3. Skill Analyzer: skill level requirement for Lv.11 and ∞. Song input/unlock status must be met as well, or just use the unlock all songs option in the WebUI.
+2 -1
View File
@@ -1,6 +1,7 @@
@echo off
for /f %%a in ('dir "webui\asset\audio\" /b /s /a-d ^| findstr ".wma"') do (
ffmpeg -i "%%~fa" -vn -ar 44100 -ac 2 -b:a 192k "%%~dpa%%~na.mp3"
echo Converting %%~fa
ffmpeg -loglevel error -y -i "%%~fa" -vn -ar 44100 -ac 2 -b:a 192k "%%~dpa%%~na.mp3"
del %%~fa
)
@echo on
+1
View File
@@ -0,0 +1 @@
find . -type f -name "*.wma" -exec bash -c 'echo "Converting $1" && ffmpeg -loglevel error -y -i $1 -vn -ar 44100 -ac 2 -b:a 192k ${1%.wma}.mp3 && rm $1' bash {} \;
+2 -2
View File
@@ -5187,7 +5187,7 @@ export const COURSES6 = [
{
id: 14,
name: 'SKILL ANALYZER 第6回',
isNew: 1,
isNew: 0,
hasGod: 1,
courses: [
{
@@ -5602,7 +5602,7 @@ export const COURSES6 = [
{
id: 16,
name: 'KAC(2023)予選コース',
isNew: 1,
isNew: 0,
courses: [
{
id: 1,
+74
View File
@@ -0,0 +1,74 @@
export const textureslist = [
{
'file': '/data/graphics/ver06/arena_rank.ifs',
'asset_folder': 'arena_rank',
'md5': '0311d56e5db4fe276aaab4435cb52354',
'textures': [
['glow.png', 2890640, 16288, [2, 570, 2, 106], [0, 572, 0, 108]],
['mixstar.png', 2712288, 48206, [2, 542, 110, 650], [0, 544, 108, 652]],
['u1.png', 457712, 149908, [574, 1114, 2, 542], [572, 1116, 0, 544]],
['u2.png', 153136, 152586, [1118, 1658, 2, 542], [1116, 1660, 0, 544]],
['u3.png', 0, 153136, [546, 1086, 546, 1086], [544, 1088, 544, 1088]],
['u4.png', 305728, 151974, [1090, 1630, 546, 1086], [1088, 1632, 544, 1088]],
['u.png', 607632, 147016, [2, 542, 654, 1194], [0, 544, 652, 1196]],
['s1.png', 1067584, 102100, [546, 1086, 1090, 1630], [544, 1088, 1088, 1632]],
['s2.png', 860160, 103886, [1090, 1630, 1090, 1630], [1088, 1632, 1088, 1632]],
['s3.png', 754656, 105494, [2, 542, 1198, 1738], [0, 544, 1196, 1740]],
['s4.png', 964048, 103534, [1662, 2202, 2, 542], [1660, 2204, 0, 544]],
['a1.png', 1856768, 93336, [2206, 2746, 2, 542], [2204, 2748, 0, 544]],
['a2.png', 1666144, 96328, [2750, 3290, 2, 542], [2748, 3292, 0, 544]],
['a3.png', 1568784, 97346, [3294, 3834, 2, 542], [3292, 3836, 0, 544]],
['a4.png', 1762480, 94284, [1634, 2174, 546, 1086], [1632, 2176, 544, 1088]],
['b1.png', 2484560, 86781, [2178, 2718, 546, 1086], [2176, 2720, 544, 1088]],
['b2.png', 2220768, 88790, [2722, 3262, 546, 1086], [2720, 3264, 544, 1088]],
['b3.png', 2041344, 90088, [3266, 3806, 546, 1086], [3264, 3808, 544, 1088]],
['b4.png', 2309568, 87663, [1634, 2174, 1090, 1630], [1632, 2176, 1088, 1632]],
['c1.png', 1470944, 97837, [2178, 2718, 1090, 1630], [2176, 2720, 1088, 1632]],
['c2.png', 1271488, 100109, [2722, 3262, 1090, 1630], [2720, 3264, 1088, 1632]],
['c3.png', 1169696, 101777, [3266, 3806, 1090, 1630], [3264, 3808, 1088, 1632]],
['c4.png', 1371600, 99342, [546, 1086, 1634, 2174], [544, 1088, 1632, 2176]],
['d1.png', 2571344, 85426, [1090, 1630, 1634, 2174], [1088, 1632, 1632, 2176]],
['d2.png', 2131440, 89314, [1634, 2174, 1634, 2174], [1632, 2176, 1632, 2176]],
['d3.png', 1950112, 91226, [2178, 2718, 1634, 2174], [2176, 2720, 1632, 2176]],
['d4.png', 2397232, 87321, [2722, 3262, 1634, 2174], [2720, 3264, 1632, 2176]],
['none.png', 2656784, 55490, [3266, 3806, 1634, 2174], [3264, 3808, 1632, 2176]],
['fudawinstat.png', 2922368, 9846, [2, 514, 1742, 1826], [0, 516, 1740, 1828]]
]
},
{
'file': '/data/graphics/ver06/psd_level.ifs',
'asset_folder': 'difficulty',
'md5': '26ef89b4550cac6156fa78ef4e59e8c3',
'textures': [
['level_small_xcd.png', 193824, 2181, [710, 922, 2, 50], [708, 924, 0, 52]]
]
},
{
'file': '/data/graphics/ver06/psd_skill.ifs',
'asset_folder': 'skill_lv',
'md5': '9a5684960c0d86cd18f1c64f6e13c98b',
'textures': [
['skill_frame_normal_sp.png', 549552, 45470, [2, 602, 242, 582], [0, 604, 240, 584]],
['skill_frame_normal_silver.png', 884448, 32373, [2, 602, 586, 926], [0, 604, 584, 928]],
['skill_frame_normal_gold.png', 850288, 34158, [610, 1210, 2, 342], [608, 1212, 0, 344]],
['skill_frame_god_sp_none.png', 595024, 40145, [1214, 1814, 2, 342], [1212, 1816, 0, 344]],
['skill_frame_god_sp.png', 195312, 80745, [606, 1206, 346, 686], [604, 1208, 344, 688]],
['skill_frame_god_silver.png', 346064, 53343, [1210, 1810, 346, 686], [1208, 1812, 344, 688]],
['skill_frame_god_gold.png', 276064, 69987, [606, 1206, 690, 1030], [604, 1208, 688, 1032]],
['skill_none.png', 1227792, 10209, [2, 454, 930, 1058], [0, 456, 928, 1060]],
['skill_12.png', 1012656, 20206, [458, 910, 1034, 1162], [456, 912, 1032, 1164]],
['skill_11.png', 1050144, 14880, [914, 1366, 1034, 1162], [912, 1368, 1032, 1164]],
['skill_10.png', 1156848, 12906, [1370, 1822, 1034, 1162], [1368, 1824, 1032, 1164]],
['skill_09.png', 1169760, 12873, [2, 454, 1062, 1190], [0, 456, 1060, 1192]],
['skill_08.png', 1091424, 13181, [458, 910, 1166, 1294], [456, 912, 1164, 1296]],
['skill_07.png', 1065024, 13193, [914, 1366, 1166, 1294], [912, 1368, 1164, 1296]],
['skill_06.png', 1078224, 13189, [1370, 1822, 1166, 1294], [1368, 1824, 1164, 1296]],
['skill_05.png', 1104608, 13144, [2, 454, 1194, 1322], [0, 456, 1192, 1324]],
['skill_04.png', 1182640, 12353, [458, 910, 1298, 1426], [456, 912, 1296, 1428]],
['skill_03.png', 1117760, 13079, [914, 1366, 1298, 1426], [912, 1368, 1296, 1428]],
['skill_02.png', 1130848, 13045, [1370, 1822, 1298, 1426], [1368, 1824, 1296, 1428]],
['skill_01.png', 1143904, 12943, [2, 454, 1326, 1454], [0, 456, 1324, 1456]],
['skill_00.png', 1217568, 10211, [458, 910, 1430, 1558], [456, 912, 1428, 1560]]
]
}
]
+136 -2
View File
@@ -4,7 +4,10 @@ import { ValgeneTicket } from '../models/valgene_ticket';
import { Skill } from '../models/skill';
import { getVersion, IDToCode, GetCounter } from '../utils';
import { Mix } from '../models/mix';
import { PREGENE } from '../data/exg';
import { PREGENE, COURSES6} from '../data/exg';
import { textureslist } from '../data/webui'
import * as fs from 'fs';
import { PNG } from '../webui/asset/js/pngjs/png.js';
export const updateProfile = async (data: {
refid: string;
@@ -363,7 +366,7 @@ export const copyResourcesFromGame = async (data: {}, send: WebUISend) => {
if(nemsys.name.match(/([0-9]+)/g) != undefined) {
let nemsysId = parseInt(nemsys.name.match(/([0-9]+)/g)[0])
if(nemsysId && resourceJsonData.nemsys.find(nem => nem.value == nemsysId) == undefined) {
if(nemsysId && ![8, 9, 10, 11].includes(nemsysId) && resourceJsonData.nemsys.find(nem => nem.value == nemsysId) == undefined) {
console.log("[nemsys] adding to json: " + nemsys.name)
resourceJsonData.nemsys.push({"value": nemsysId, "name": nemsys.name})
}
@@ -551,9 +554,140 @@ export const copyResourcesFromGame = async (data: {}, send: WebUISend) => {
await IO.WriteFile('webui/asset/json/data.json', JSON.stringify(resourceJsonData, null, 4))
await IO.WriteFile('webui/asset/json/appeal.json', JSON.stringify(apCardJsonData, null, 4))
// Extract textures from ifs files using pngjs. Massive thanks to https://github.com/mon/ifstools
console.log("Extracting textures from IFS files")
let ifsSuccess = []
for(let listIter = 0; listIter < textureslist.length; listIter++) {
let manifestJson = {}
let bufOffset = 0
let magic = '6CAD8F89'
console.log(textureslist[listIter]['file'] + ":")
let ifsBuffer = await IO.ReadFile(U.GetConfig('sdvx_eg_root_dir') + textureslist[listIter].file, {flag: 'r'})
if(!fs.existsSync('plugins/sdvx@asphyxia/webui/asset/' + textureslist[listIter].asset_folder)) {
fs.mkdirSync('plugins/sdvx@asphyxia/webui/asset/' + textureslist[listIter].asset_folder)
}
let header = Buffer.from(ifsBuffer.buffer.slice(0, 36))
let sig = header.readUInt32BE().toString(16).toUpperCase()
bufOffset += 4
if(sig === magic) {
bufOffset += 12
let manifest_end = ifsBuffer.readUInt32BE(bufOffset)
bufOffset += 4
let md5_hash = ifsBuffer.toString('hex', bufOffset, bufOffset + 16)
bufOffset += 16
if(md5_hash === textureslist[listIter].md5) {
for(let texIter = 0; texIter < textureslist[listIter]['textures'].length; texIter++) {
let tdFileName = textureslist[listIter]['textures'][texIter][0]
let tdOffset = parseInt(textureslist[listIter]['textures'][texIter][1].toString())
let tdSize = parseInt(textureslist[listIter]['textures'][texIter][2].toString())
let tdUvrect = textureslist[listIter]['textures'][texIter][3]
let tdImgRect = textureslist[listIter]['textures'][texIter][4]
let imgBufferHead = Buffer.from(ifsBuffer.buffer.slice(manifest_end + tdOffset, manifest_end + tdOffset + 8))
let imgBufferHeadOff = 0
let imgBuffer = Buffer.from(ifsBuffer.buffer.slice(manifest_end + tdOffset + 8, manifest_end + tdOffset + tdSize))
let imgBufferOff = 0
let imgUncompressedSize = imgBufferHead.readUInt32BE(imgBufferHeadOff)
imgBufferHeadOff += 4
let imgCompressedSize = imgBufferHead.readUInt32BE(imgBufferHeadOff)
imgBufferHeadOff += 4
// Decompression algorithm & code from ifstools/handlers/lz77.py
let decompressed = []
let contLoop = true
let diff, flag, w, position, length
while(contLoop) {
flag = imgBuffer.readUInt8(imgBufferOff)
imgBufferOff++
for(let i = 0; i < 8; i++){
if (((flag >> i) & 1) === 1) {
decompressed.push(imgBuffer.readUInt8(imgBufferOff))
imgBufferOff++
}
else {
w = imgBuffer.readUInt16BE(imgBufferOff)
imgBufferOff+=2
position = (w >> 4)
length = (w & 0x0F) + 3
if(position === 0) {
contLoop = false
break;
}
if(position > decompressed.length) {
diff = 0
diff = position - decompressed.length
diff = Math.min(diff, length)
for(let e2p = 0; e2p < diff; e2p++) {
decompressed.push(0)
}
length -= diff
}
if (-position+length < 0) {
decompressed.push(...decompressed.slice(decompressed.length + (-position), decompressed.length + (-position+length)))
}
else {
for(let loop = 0; loop < length; loop++) {
decompressed.push(decompressed[decompressed.length + (-position)])
}
}
}
}
}
// Swap red and blue data (RGBA -> BGRA)
if(decompressed.length === imgUncompressedSize) {
for(let decCtr = 0; decCtr < decompressed.length; decCtr += 4) {
decompressed[decCtr + 2] = [decompressed[decCtr], decompressed[decCtr] = decompressed[decCtr + 2]][0];
}
let pngf = new PNG({
width: Math.floor(tdImgRect[1]/2) - Math.floor(tdImgRect[0]/2),
height: Math.floor(tdImgRect[3]/2) - Math.floor(tdImgRect[2]/2),
bitDepth: 8,
colorType: 6,
inputHasAlpha: true
})
pngf.data = Buffer.from(decompressed)
const outputStream = await fs.createWriteStream('plugins/sdvx@asphyxia/webui/asset/' + textureslist[listIter].asset_folder + '/' + tdFileName);
await pngf.pack().pipe(outputStream);
console.log(' - ' + tdFileName + ' created successfully.');
ifsSuccess.push(textureslist[listIter].file + ' - ' + tdFileName)
} else {
console.log("Decompression mismatch.")
runErrors.push('[ifs] decompression mismatch for ' + textureslist[listIter]['file'] + '/' + tdFileName)
}
}
} else {
console.log('MD5 mismatch.')
runErrors.push('[ifs] MD5 mismatch - ' + textureslist[listIter].file)
}
} else {
console.log('IFS file unsupported/invalid.')
runErrors.push('[ifs] IFS file "' + textureslist[listIter].file + '" unsupported/invalid.')
}
}
console.log("Updating course_data.json")
let courseDataUpdateSuccess = false
let courseData = JSON.parse(U.DecodeString(await IO.ReadFile('webui/asset/json/course_data.json'), 'utf8'))
for(let cIter = 0; cIter < courseData.courseData.length; cIter++) {
if(courseData.courseData[cIter].version === 6) {
courseData.courseData[cIter].info = COURSES6
courseDataUpdateSuccess = true
}
}
if(courseDataUpdateSuccess === false) runErrors.push('[course_data] Update unsuccessful.')
await IO.WriteFile('webui/asset/json/course_data.json', JSON.stringify(courseData, null, 4))
send.json(
{
status: 'ok',
course: courseDataUpdateSuccess,
ifs: ifsSuccess,
akaname: newAkanames,
nemsys: newNemsysData,
apCard: newAPCardData,
+74 -36
View File
@@ -1,6 +1,6 @@
var music_db, course_db, score_db, data_db, appeal_db;
var music_db, course_db, score_db, data_db, appeal_db, skill_title_db;
var volforceArray = [];
var profile_data, skill_data;
var profile_data, skill_data, course_data;
var baseTBodyCMpD, baseTBodyCMpL, baseTBodyGpD, baseTBodyGpL, baseTBodyASpL;
var notFirst = false;
var versionText = ['', 'BOOTH', 'INFINTE INFECTION', 'GRAVITY WARS', 'HEAVENLY HAVEN', 'VIVIDWAVE', 'EXCEED GEAR']
@@ -24,7 +24,16 @@ function zeroPad(num, places) {
function getSkillAsset(skill) {
return "static/asset/skill_lv/skill_" + zeroPad(skill, 2) + ".png";
return "static/asset/skill_lv/skill_" + ((skill === -1) ? 'none' : zeroPad(skill, 2)) + ".png";
}
function getSkillFrameAsset(frame) {
return "static/asset/skill_lv/skill_frame_" + frame + ".png";
}
function getSkillTitle() {
if(skill_data.length <= 0 || skill_data[0].name === (undefined || 0)) return ''
return skill_title_db.filter(e => e.id === skill_data[0].name)[0].name
}
function getGrade(grade) {
@@ -634,18 +643,47 @@ function setUpStatistics() {
$('#version_select').change(function() {
$('#skillLV').fadeOut(200, () => {
console.log("change version select");
$('#skillLV').attr('src', getSkillAsset(getPlayerSkill($('#version_select').val())))
$('#skillLV').attr('src', getSkillAsset(getPlayerSkill($('#version_select').val())[0]))
});
$('#skillLV').fadeIn(200);
});
function getPlayerSkill(version) {
// console.log(getPlayerMaxVersion())
if (skill_data.length == 0) return 0;
if (skill_data.length == 0) return [-1, 0];
var k = skill_data.filter(e => e.version == version)
return parseInt(k[0].level);
let courseDataFil = course_data.filter(e => ![6,7,12,13,15,16].includes(e.sid) && e.cid === k[0].level && ((k[0].type !== undefined) ? k[0].type : 0) === ((e.stype !== undefined) ? e.stype : 0))
return [(courseDataFil.length > 0) ? k[0].level : -1, (k[0].type !== undefined) ? k[0].type : 0];
}
function getPlayerCourse(version, playerSkill) {
if(version === 0) return 'none'
let skillFrame = ['normal', 'god']
let skillThrsh = [[130, 150, 160, 170], [150, 160, 170, 180]]
let skillThrshVal = (playerSkill[0] <= 9) ? 0 : playerSkill[0] - 9
var k = course_db.courseData.filter(e => e.version == version)[0].info
let sidCourses = []
k.forEach(kd => {
kd.courses.forEach(kdc => {
kdc['sid'] = kd.id
kdc['isNew'] = kd.isNew
sidCourses.push(kdc)
})
})
sidCourses = sidCourses.filter(e => ![6,7,12,13,15,16].includes(e.sid) && e.level === playerSkill[0])
let newCourses = sidCourses.filter(e => e.isNew === 1).map(a => a.sid)
let foundCourses = course_data.filter(e => e.cid === playerSkill[0] && ((e.stype !== undefined) ? e.stype : 0) === playerSkill[1] && e.clear >= 2)
let newCompleteCourses = foundCourses.filter(e => newCourses.includes(e.sid))
let thrshCourses = foundCourses.filter(e => Math.floor(e.rate/100) >= skillThrsh[playerSkill[1]][skillThrshVal])
if(sidCourses.length > 0 && thrshCourses.length === sidCourses.length) return skillFrame[playerSkill[1]] + '_sp'
if(sidCourses.length > 0 && foundCourses.length === sidCourses.length) return skillFrame[playerSkill[1]] + '_gold'
if(newCompleteCourses.length > 1) return skillFrame[playerSkill[1]] + '_silver'
if(foundCourses.length > 1 && playerSkill[1] === 1) return skillFrame[playerSkill[1]] + '_sp_none'
return 'none'
}
function getVersionSelect() {
if (skill_data.length == 0) return [];
var versionDATA = [];
@@ -662,10 +700,10 @@ function displayArenaSeasonData(season) {
$('.arena-details').append(
$('<div class="tile is-parent is-7">').append(
$('<article class="tile is-child arena-details-child-left">').append(
// $('<img>', {
// src: 'static/asset/arena_icon/' + rankInfo[0] + ".png"
// })
$('<h1 style="font-size:100px">' + ((rankInfo[0] === 'none') ? 'n/a' : rankInfo[0].toUpperCase()) + "</h1>")
$('<img>', {
src: 'static/asset/arena_rank/' + rankInfo[0] + ".png"
})
// $('<h1 style="font-size:100px">' + ((rankInfo[0] === 'none') ? 'n/a' : rankInfo[0].toUpperCase()) + "</h1>")
)
)
).append(
@@ -674,10 +712,6 @@ function displayArenaSeasonData(season) {
$('<p class="title" style="font-family: testfont">Arena Power</p>').append(
$('<div class="content">' + sznData.shopPoint + ' AP </div>')
)
).append(
(sznData.megamixRate !== (0 || undefined)) ? $('<p class="title" style="font-family: testfont">Megamix Rate</p>').append(
$('<div class="content">' + sznData.megamixRate + '</div>')
) : $("<p></p>")
)
)
)
@@ -695,12 +729,12 @@ function displayArenaSeasonData(season) {
$('<meter id="rank-point-mtr" style="width:80%" min="' + rankInfo[1] + '"max="' + ((rankInfo[2] !== undefined) ? rankInfo[2].point : rankInfo[1]) + '" value="' + sznData.rankPoint + '"></meter>')
).append(
$('<h5 style="font-family: testfont">' + sznData.rankPoint + ' pts (' + (rankInfo[2].point - sznData.rankPoint) + ' pts to ' + rankInfo[2].rank.toUpperCase() + ')</h5>')
).append(
(sznData.megamixRate !== (0 || undefined)) ? $('<h5 style="font-family: testfont">Megamix rate: ' + sznData.megamixRate + ' <img style="width:20px" src="static/asset/arena_rank/mixstar.png"></p></div></h5>') : $('<h5>')
)
} else if(rankInfo[0] !== 'none'){
$('.arena-details-child-left').append(
$('<h5 style="font-family:testfont">Ultimate rate: ' + sznData.ultimateRate + '</h5>')
).append(
(sznData.megamixRate !== (0 || undefined)) ? $('<h5 style="font-family: testfont">Megamix rate: ' + sznData.megamixRate + ' <img style="width:20px" src="static/asset/arena_rank/mixstar.png"></p></div></h5>') : $('<h5 style="font-family:testfont">Ultimate rate: ' + sznData.ultimateRate + '</h5>')
)
}
}
@@ -754,6 +788,7 @@ $(document).ready(function() {
profile_data = JSON.parse(document.getElementById("data-pass").innerText);
score_db = JSON.parse(document.getElementById("score-pass").innerText);
skill_data = JSON.parse(document.getElementById("skill-pass").innerText);
course_data = JSON.parse(document.getElementById("course-pass").innerText);
arena_data = JSON.parse(document.getElementById("arena-pass").innerText);
skill_data.sort(function(a, b) {
@@ -782,7 +817,10 @@ $(document).ready(function() {
$.getJSON("static/asset/json/appeal.json", function(json) {
appeal_db = json;
//console.log(appeal_db);
})
}),
$.getJSON("static/asset/json/customize_data_ext.json", function(json) {
skill_title_db = json.skilltitle;
}),
).then(function() {
var currentVF = calculateVolforce();
var maxVer = skill_data.length > 0 ? parseInt(skill_data[0]["version"]) : 0
@@ -809,7 +847,7 @@ $(document).ready(function() {
}
$('#test').append(
$('<div class="card is-inlineblocked">').append(
$('<div class="card is-inlineblocked" style="padding-bottom:30px">').append(
$('<div class="card-header">').append(
$('<p class="card-header-title">').append(
$('<span class="icon">').append(
@@ -873,32 +911,27 @@ $(document).ready(function() {
).css('width', '100%')
).css('vertical-align', 'top')
.css('max-width', '100%')
).append(
$('<div class="card is-inlineblocked">').append(
$('<div class="card-header">').append(
$('<p class="card-header-title">').append(
$('<span class="icon">').append(
$('<i class="mdi mdi-pulse">')
)
).append("Other Data")
)
).append(
.append(
$('<div class="card-content">').append(
$('<div class="tile is-ancestor">').append(
$('<div class="tile is-parent is-7">').append(
$('<article class="tile is-child box">').append(
$('<p class="title">').append(
"Skill Level"
$('<article class="tile is-child">').append(
$('<div class="content" style="position: relative;display: flex;justify-content: center;align-items: center;">').append(
$('<img id="skillLV">').attr('src', getSkillAsset(getPlayerSkill(maxVer)[0]))
).append(
$('<div class="content">').append(
$('<img id="skillLV">').attr('src', getSkillAsset(getPlayerSkill(maxVer)))
$('<div style="position:absolute;width:100%;height:100%display: flex;justify-content: center;align-items: center;">').append(
$('<img id="skillFrame" style="height:100%">')
)
).css('font-family', "testfont")
).append(
$('<div style="position:absolute;padding-left:20px;width:100%;height:100%;display: flex;justify-content: center;align-items: center;">').append(
$('<p id="skillTitle"></p>')
)
)
)
)
).append(
$('<div class="tile is-parent is-5">').append(
$('<article class="tile is-child box">').append(
$('<article class="tile is-child">').append(
$('<p class="title">').append(
"PCB"
).append(
@@ -1031,6 +1064,11 @@ $(document).ready(function() {
displayArenaSeasonData($('#arena-szn-sel').val())
});
let skillFrame = getPlayerCourse(maxVer, getPlayerSkill(maxVer))
if(skillFrame !== 'none') $('#skillFrame').attr('src', getSkillFrameAsset(skillFrame))
$('#skillTitle').text(getSkillTitle())
$('#skillTitle').attr('style', 'font-size:25px; color:black; font-weight:bold;color:' + (getPlayerSkill(maxVer)[0] === 11 ? '#FFC100' : ((getPlayerSkill(maxVer)[0] === 12) ? '#FFE000' : "black")))
setUpStatistics();
setCMpD();
displayArenaSeasonData($('#arena-szn-sel').val())
+4 -3
View File
@@ -65,9 +65,10 @@ async function loadItems(itemSet, gene_edition, items_crew, items_stamp, items_s
}
$('.count').text('Unlocked items: ' + itemCounts[0] + "/" + itemCounts[1])
if(itemSet === 1) {
$('.setinfo').append('<p class="jpn-excl" style="padding:5px">These items are only usable in Japan. Change region to Japan in the ea3-config.xml file.</p>')
$('.count').text('Obtained items: ' + itemCounts[0] + "/" + itemCounts[1])
if(geneItems.jpn_exc) {
$('.jpn-excl').remove()
$('.setinfo').append('<p class="jpn-excl" style="padding:5px">These items are only usable if your game region is set to Japan. Change your game region through either modifying ea3-config.xml or dll patch.</p>')
} else {
$('.jpn-excl').remove()
}
@@ -0,0 +1,267 @@
"use strict";
let interlaceUtils = require("./interlace");
let pixelBppMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos === data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = 0xff;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, data, pxPos, rawPos) {
if (rawPos + 1 >= data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = data[rawPos + 1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos + 2 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = 0xff;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, data, pxPos, rawPos) {
if (rawPos + 3 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = data[rawPos + 3];
},
];
let pixelBppCustomMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = maxBit;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, pixelData, pxPos) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = pixelData[1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = maxBit;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, pixelData, pxPos) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = pixelData[3];
},
];
function bitRetriever(data, depth) {
let leftOver = [];
let i = 0;
function split() {
if (i === data.length) {
throw new Error("Ran out of data");
}
let byte = data[i];
i++;
let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1;
switch (depth) {
default:
throw new Error("unrecognised depth");
case 16:
byte2 = data[i];
i++;
leftOver.push((byte << 8) + byte2);
break;
case 4:
byte2 = byte & 0x0f;
byte1 = byte >> 4;
leftOver.push(byte1, byte2);
break;
case 2:
byte4 = byte & 3;
byte3 = (byte >> 2) & 3;
byte2 = (byte >> 4) & 3;
byte1 = (byte >> 6) & 3;
leftOver.push(byte1, byte2, byte3, byte4);
break;
case 1:
byte8 = byte & 1;
byte7 = (byte >> 1) & 1;
byte6 = (byte >> 2) & 1;
byte5 = (byte >> 3) & 1;
byte4 = (byte >> 4) & 1;
byte3 = (byte >> 5) & 1;
byte2 = (byte >> 6) & 1;
byte1 = (byte >> 7) & 1;
leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8);
break;
}
}
return {
get: function (count) {
while (leftOver.length < count) {
split();
}
let returner = leftOver.slice(0, count);
leftOver = leftOver.slice(count);
return returner;
},
resetAfterLine: function () {
leftOver.length = 0;
},
end: function () {
if (i !== data.length) {
throw new Error("extra data found");
}
},
};
}
function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pxPos = getPxPos(x, y, imagePass);
pixelBppMapper[bpp](pxData, data, pxPos, rawPos);
rawPos += bpp; //eslint-disable-line no-param-reassign
}
}
return rawPos;
}
function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pixelData = bits.get(bpp);
let pxPos = getPxPos(x, y, imagePass);
pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit);
}
bits.resetAfterLine();
}
}
exports.dataToBitMap = function (data, bitmapInfo) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let depth = bitmapInfo.depth;
let bpp = bitmapInfo.bpp;
let interlace = bitmapInfo.interlace;
let bits;
if (depth !== 8) {
bits = bitRetriever(data, depth);
}
let pxData;
if (depth <= 8) {
pxData = Buffer.alloc(width * height * 4);
} else {
pxData = new Uint16Array(width * height * 4);
}
let maxBit = Math.pow(2, depth) - 1;
let rawPos = 0;
let images;
let getPxPos;
if (interlace) {
images = interlaceUtils.getImagePasses(width, height);
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
} else {
let nonInterlacedPxPos = 0;
getPxPos = function () {
let returner = nonInterlacedPxPos;
nonInterlacedPxPos += 4;
return returner;
};
images = [{ width: width, height: height }];
}
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
if (depth === 8) {
rawPos = mapImage8Bit(
images[imageIndex],
pxData,
getPxPos,
bpp,
data,
rawPos
);
} else {
mapImageCustomBit(
images[imageIndex],
pxData,
getPxPos,
bpp,
bits,
maxBit
);
}
}
if (depth === 8) {
if (rawPos !== data.length) {
throw new Error("extra data found");
}
} else {
bits.end();
}
return pxData;
};
@@ -0,0 +1,158 @@
"use strict";
let constants = require("./constants");
module.exports = function (dataIn, width, height, options) {
let outHasAlpha =
[constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
options.colorType
) !== -1;
if (options.colorType === options.inputColorType) {
let bigEndian = (function () {
let buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
// Int16Array uses the platform's endianness.
return new Int16Array(buffer)[0] !== 256;
})();
// If no need to convert to grayscale and alpha is present/absent in both, take a fast route
if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) {
return dataIn;
}
}
// map to a UInt16 array if data is 16bit, fix endianness below
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
let maxValue = 255;
let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
if (inBpp === 4 && !options.inputHasAlpha) {
inBpp = 3;
}
let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
if (options.bitDepth === 16) {
maxValue = 65535;
outBpp *= 2;
}
let outData = Buffer.alloc(width * height * outBpp);
let inIndex = 0;
let outIndex = 0;
let bgColor = options.bgColor || {};
if (bgColor.red === undefined) {
bgColor.red = maxValue;
}
if (bgColor.green === undefined) {
bgColor.green = maxValue;
}
if (bgColor.blue === undefined) {
bgColor.blue = maxValue;
}
function getRGBA() {
let red;
let green;
let blue;
let alpha = maxValue;
switch (options.inputColorType) {
case constants.COLORTYPE_COLOR_ALPHA:
alpha = data[inIndex + 3];
red = data[inIndex];
green = data[inIndex + 1];
blue = data[inIndex + 2];
break;
case constants.COLORTYPE_COLOR:
red = data[inIndex];
green = data[inIndex + 1];
blue = data[inIndex + 2];
break;
case constants.COLORTYPE_ALPHA:
alpha = data[inIndex + 1];
red = data[inIndex];
green = red;
blue = red;
break;
case constants.COLORTYPE_GRAYSCALE:
red = data[inIndex];
green = red;
blue = red;
break;
default:
throw new Error(
"input color type:" +
options.inputColorType +
" is not supported at present"
);
}
if (options.inputHasAlpha) {
if (!outHasAlpha) {
alpha /= maxValue;
red = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0),
maxValue
);
green = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0),
maxValue
);
blue = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0),
maxValue
);
}
}
return { red: red, green: green, blue: blue, alpha: alpha };
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let rgba = getRGBA(data, inIndex);
switch (options.colorType) {
case constants.COLORTYPE_COLOR_ALPHA:
case constants.COLORTYPE_COLOR:
if (options.bitDepth === 8) {
outData[outIndex] = rgba.red;
outData[outIndex + 1] = rgba.green;
outData[outIndex + 2] = rgba.blue;
if (outHasAlpha) {
outData[outIndex + 3] = rgba.alpha;
}
} else {
outData.writeUInt16BE(rgba.red, outIndex);
outData.writeUInt16BE(rgba.green, outIndex + 2);
outData.writeUInt16BE(rgba.blue, outIndex + 4);
if (outHasAlpha) {
outData.writeUInt16BE(rgba.alpha, outIndex + 6);
}
}
break;
case constants.COLORTYPE_ALPHA:
case constants.COLORTYPE_GRAYSCALE: {
// Convert to grayscale and alpha
let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
if (options.bitDepth === 8) {
outData[outIndex] = grayscale;
if (outHasAlpha) {
outData[outIndex + 1] = rgba.alpha;
}
} else {
outData.writeUInt16BE(grayscale, outIndex);
if (outHasAlpha) {
outData.writeUInt16BE(rgba.alpha, outIndex + 2);
}
}
break;
}
default:
throw new Error("unrecognised color Type " + options.colorType);
}
inIndex += inBpp;
outIndex += outBpp;
}
}
return outData;
};
@@ -0,0 +1,189 @@
"use strict";
let util = require("util");
let Stream = require("stream");
let ChunkStream = (module.exports = function () {
Stream.call(this);
this._buffers = [];
this._buffered = 0;
this._reads = [];
this._paused = false;
this._encoding = "utf8";
this.writable = true;
});
util.inherits(ChunkStream, Stream);
ChunkStream.prototype.read = function (length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback,
});
process.nextTick(
function () {
this._process();
// its paused and there is not enought data then ask for more
if (this._paused && this._reads && this._reads.length > 0) {
this._paused = false;
this.emit("drain");
}
}.bind(this)
);
};
ChunkStream.prototype.write = function (data, encoding) {
if (!this.writable) {
this.emit("error", new Error("Stream not writable"));
return false;
}
let dataBuffer;
if (Buffer.isBuffer(data)) {
dataBuffer = data;
} else {
dataBuffer = Buffer.from(data, encoding || this._encoding);
}
this._buffers.push(dataBuffer);
this._buffered += dataBuffer.length;
this._process();
// ok if there are no more read requests
if (this._reads && this._reads.length === 0) {
this._paused = true;
}
return this.writable && !this._paused;
};
ChunkStream.prototype.end = function (data, encoding) {
if (data) {
this.write(data, encoding);
}
this.writable = false;
// already destroyed
if (!this._buffers) {
return;
}
// enqueue or handle end
if (this._buffers.length === 0) {
this._end();
} else {
this._buffers.push(null);
this._process();
}
};
ChunkStream.prototype.destroySoon = ChunkStream.prototype.end;
ChunkStream.prototype._end = function () {
if (this._reads.length > 0) {
this.emit("error", new Error("Unexpected end of input"));
}
this.destroy();
};
ChunkStream.prototype.destroy = function () {
if (!this._buffers) {
return;
}
this.writable = false;
this._reads = null;
this._buffers = null;
this.emit("close");
};
ChunkStream.prototype._processReadAllowingLess = function (read) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
// first we need to peek into first buffer
let smallerBuf = this._buffers[0];
// ok there is more data than we need
if (smallerBuf.length > read.length) {
this._buffered -= read.length;
this._buffers[0] = smallerBuf.slice(read.length);
read.func.call(this, smallerBuf.slice(0, read.length));
} else {
// ok this is less than maximum length so use it all
this._buffered -= smallerBuf.length;
this._buffers.shift(); // == smallerBuf
read.func.call(this, smallerBuf);
}
};
ChunkStream.prototype._processRead = function (read) {
this._reads.shift(); // == read
let pos = 0;
let count = 0;
let data = Buffer.alloc(read.length);
// create buffer for all data
while (pos < read.length) {
let buf = this._buffers[count++];
let len = Math.min(buf.length, read.length - pos);
buf.copy(data, pos, 0, len);
pos += len;
// last buffer wasn't used all so just slice it and leave
if (len !== buf.length) {
this._buffers[--count] = buf.slice(len);
}
}
// remove all used buffers
if (count > 0) {
this._buffers.splice(0, count);
}
this._buffered -= read.length;
read.func.call(this, data);
};
ChunkStream.prototype._process = function () {
try {
// as long as there is any data and read requests
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
let read = this._reads[0];
// read any data (but no more than length)
if (read.allowLess) {
this._processReadAllowingLess(read);
} else if (this._buffered >= read.length) {
// ok we can meet some expectations
this._processRead(read);
} else {
// not enought data to satisfy first request in queue
// so we need to wait for more
break;
}
}
if (this._buffers && !this.writable) {
this._end();
}
} catch (ex) {
this.emit("error", ex);
}
};
@@ -0,0 +1,32 @@
"use strict";
module.exports = {
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
TYPE_IHDR: 0x49484452,
TYPE_IEND: 0x49454e44,
TYPE_IDAT: 0x49444154,
TYPE_PLTE: 0x504c5445,
TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase
TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase
// color-type bits
COLORTYPE_GRAYSCALE: 0,
COLORTYPE_PALETTE: 1,
COLORTYPE_COLOR: 2,
COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha
// color-type combinations
COLORTYPE_PALETTE_COLOR: 3,
COLORTYPE_COLOR_ALPHA: 6,
COLORTYPE_TO_BPP_MAP: {
0: 1,
2: 3,
3: 1,
4: 2,
6: 4,
},
GAMMA_DIVISION: 100000,
};
+40
View File
@@ -0,0 +1,40 @@
"use strict";
let crcTable = [];
(function () {
for (let i = 0; i < 256; i++) {
let currentCrc = i;
for (let j = 0; j < 8; j++) {
if (currentCrc & 1) {
currentCrc = 0xedb88320 ^ (currentCrc >>> 1);
} else {
currentCrc = currentCrc >>> 1;
}
}
crcTable[i] = currentCrc;
}
})();
let CrcCalculator = (module.exports = function () {
this._crc = -1;
});
CrcCalculator.prototype.write = function (data) {
for (let i = 0; i < data.length; i++) {
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
}
return true;
};
CrcCalculator.prototype.crc32 = function () {
return this._crc ^ -1;
};
CrcCalculator.crc32 = function (buf) {
let crc = -1;
for (let i = 0; i < buf.length; i++) {
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
}
return crc ^ -1;
};
@@ -0,0 +1,171 @@
"use strict";
let paethPredictor = require("./paeth-predictor");
function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
rawData[rawPos + x] = pxData[pxPos + x];
}
}
function filterSumNone(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let i = pxPos; i < length; i++) {
sum += Math.abs(pxData[i]);
}
return sum;
}
function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
rawData[rawPos + x] = val;
}
}
function filterSumSub(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
sum += Math.abs(val);
}
return sum;
}
function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - up;
rawData[rawPos + x] = val;
}
}
function filterSumUp(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let x = pxPos; x < length; x++) {
let up = pxPos > 0 ? pxData[x - byteWidth] : 0;
let val = pxData[x] - up;
sum += Math.abs(val);
}
return sum;
}
function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
rawData[rawPos + x] = val;
}
}
function filterSumAvg(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
sum += Math.abs(val);
}
return sum;
}
function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
rawData[rawPos + x] = val;
}
}
function filterSumPaeth(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
sum += Math.abs(val);
}
return sum;
}
let filters = {
0: filterNone,
1: filterSub,
2: filterUp,
3: filterAvg,
4: filterPaeth,
};
let filterSums = {
0: filterSumNone,
1: filterSumSub,
2: filterSumUp,
3: filterSumAvg,
4: filterSumPaeth,
};
module.exports = function (pxData, width, height, options, bpp) {
let filterTypes;
if (!("filterType" in options) || options.filterType === -1) {
filterTypes = [0, 1, 2, 3, 4];
} else if (typeof options.filterType === "number") {
filterTypes = [options.filterType];
} else {
throw new Error("unrecognised filter types");
}
if (options.bitDepth === 16) {
bpp *= 2;
}
let byteWidth = width * bpp;
let rawPos = 0;
let pxPos = 0;
let rawData = Buffer.alloc((byteWidth + 1) * height);
let sel = filterTypes[0];
for (let y = 0; y < height; y++) {
if (filterTypes.length > 1) {
// find best filter for this line (with lowest sum of values)
let min = Infinity;
for (let i = 0; i < filterTypes.length; i++) {
let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp);
if (sum < min) {
sel = filterTypes[i];
min = sum;
}
}
}
rawData[rawPos] = sel;
rawPos++;
filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp);
rawPos += byteWidth;
pxPos += byteWidth;
}
return rawData;
};
@@ -0,0 +1,24 @@
"use strict";
let util = require("util");
let ChunkStream = require("./chunkstream");
let Filter = require("./filter-parse");
let FilterAsync = (module.exports = function (bitmapInfo) {
ChunkStream.call(this);
let buffers = [];
let that = this;
this._filter = new Filter(bitmapInfo, {
read: this.read.bind(this),
write: function (buffer) {
buffers.push(buffer);
},
complete: function () {
that.emit("complete", Buffer.concat(buffers));
},
});
this._filter.start();
});
util.inherits(FilterAsync, ChunkStream);
@@ -0,0 +1,21 @@
"use strict";
let SyncReader = require("./sync-reader");
let Filter = require("./filter-parse");
exports.process = function (inBuffer, bitmapInfo) {
let outBuffers = [];
let reader = new SyncReader(inBuffer);
let filter = new Filter(bitmapInfo, {
read: reader.read.bind(reader),
write: function (bufferPart) {
outBuffers.push(bufferPart);
},
complete: function () {},
});
filter.start();
reader.process();
return Buffer.concat(outBuffers);
};
@@ -0,0 +1,177 @@
"use strict";
let interlaceUtils = require("./interlace");
let paethPredictor = require("./paeth-predictor");
function getByteWidth(width, bpp, depth) {
let byteWidth = width * bpp;
if (depth !== 8) {
byteWidth = Math.ceil(byteWidth / (8 / depth));
}
return byteWidth;
}
let Filter = (module.exports = function (bitmapInfo, dependencies) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let interlace = bitmapInfo.interlace;
let bpp = bitmapInfo.bpp;
let depth = bitmapInfo.depth;
this.read = dependencies.read;
this.write = dependencies.write;
this.complete = dependencies.complete;
this._imageIndex = 0;
this._images = [];
if (interlace) {
let passes = interlaceUtils.getImagePasses(width, height);
for (let i = 0; i < passes.length; i++) {
this._images.push({
byteWidth: getByteWidth(passes[i].width, bpp, depth),
height: passes[i].height,
lineIndex: 0,
});
}
} else {
this._images.push({
byteWidth: getByteWidth(width, bpp, depth),
height: height,
lineIndex: 0,
});
}
// when filtering the line we look at the pixel to the left
// the spec also says it is done on a byte level regardless of the number of pixels
// so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back
// a pixel rather than just a different byte part. However if we are sub byte, we ignore.
if (depth === 8) {
this._xComparison = bpp;
} else if (depth === 16) {
this._xComparison = bpp * 2;
} else {
this._xComparison = 1;
}
});
Filter.prototype.start = function () {
this.read(
this._images[this._imageIndex].byteWidth + 1,
this._reverseFilterLine.bind(this)
);
};
Filter.prototype._unFilterType1 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
unfilteredLine[x] = rawByte + f1Left;
}
};
Filter.prototype._unFilterType2 = function (
rawData,
unfilteredLine,
byteWidth
) {
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f2Up = lastLine ? lastLine[x] : 0;
unfilteredLine[x] = rawByte + f2Up;
}
};
Filter.prototype._unFilterType3 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f3Up = lastLine ? lastLine[x] : 0;
let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f3Add = Math.floor((f3Left + f3Up) / 2);
unfilteredLine[x] = rawByte + f3Add;
}
};
Filter.prototype._unFilterType4 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f4Up = lastLine ? lastLine[x] : 0;
let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0;
let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft);
unfilteredLine[x] = rawByte + f4Add;
}
};
Filter.prototype._reverseFilterLine = function (rawData) {
let filter = rawData[0];
let unfilteredLine;
let currentImage = this._images[this._imageIndex];
let byteWidth = currentImage.byteWidth;
if (filter === 0) {
unfilteredLine = rawData.slice(1, byteWidth + 1);
} else {
unfilteredLine = Buffer.alloc(byteWidth);
switch (filter) {
case 1:
this._unFilterType1(rawData, unfilteredLine, byteWidth);
break;
case 2:
this._unFilterType2(rawData, unfilteredLine, byteWidth);
break;
case 3:
this._unFilterType3(rawData, unfilteredLine, byteWidth);
break;
case 4:
this._unFilterType4(rawData, unfilteredLine, byteWidth);
break;
default:
throw new Error("Unrecognised filter type - " + filter);
}
}
this.write(unfilteredLine);
currentImage.lineIndex++;
if (currentImage.lineIndex >= currentImage.height) {
this._lastLine = null;
this._imageIndex++;
currentImage = this._images[this._imageIndex];
} else {
this._lastLine = unfilteredLine;
}
if (currentImage) {
// read, using the byte width that may be from the new current image
this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this));
} else {
this._lastLine = null;
this.complete();
}
};
@@ -0,0 +1,93 @@
"use strict";
function dePalette(indata, outdata, width, height, palette) {
let pxPos = 0;
// use values from palette
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let color = palette[indata[pxPos]];
if (!color) {
throw new Error("index " + indata[pxPos] + " not in palette");
}
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = color[i];
}
pxPos += 4;
}
}
}
function replaceTransparentColor(indata, outdata, width, height, transColor) {
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let makeTrans = false;
if (transColor.length === 1) {
if (transColor[0] === indata[pxPos]) {
makeTrans = true;
}
} else if (
transColor[0] === indata[pxPos] &&
transColor[1] === indata[pxPos + 1] &&
transColor[2] === indata[pxPos + 2]
) {
makeTrans = true;
}
if (makeTrans) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = 0;
}
}
pxPos += 4;
}
}
}
function scaleDepth(indata, outdata, width, height, depth) {
let maxOutSample = 255;
let maxInSample = Math.pow(2, depth) - 1;
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = Math.floor(
(indata[pxPos + i] * maxOutSample) / maxInSample + 0.5
);
}
pxPos += 4;
}
}
}
module.exports = function (indata, imageData, skipRescale = false) {
let depth = imageData.depth;
let width = imageData.width;
let height = imageData.height;
let colorType = imageData.colorType;
let transColor = imageData.transColor;
let palette = imageData.palette;
let outdata = indata; // only different for 16 bits
if (colorType === 3) {
// paletted
dePalette(indata, outdata, width, height, palette);
} else {
if (transColor) {
replaceTransparentColor(indata, outdata, width, height, transColor);
}
// if it needs scaling
if (depth !== 8 && !skipRescale) {
// if we need to change the buffer size
if (depth === 16) {
outdata = Buffer.alloc(width * height * 4);
}
scaleDepth(indata, outdata, width, height, depth);
}
}
return outdata;
};
@@ -0,0 +1,95 @@
"use strict";
// Adam 7
// 0 1 2 3 4 5 6 7
// 0 x 6 4 6 x 6 4 6
// 1 7 7 7 7 7 7 7 7
// 2 5 6 5 6 5 6 5 6
// 3 7 7 7 7 7 7 7 7
// 4 3 6 4 6 3 6 4 6
// 5 7 7 7 7 7 7 7 7
// 6 5 6 5 6 5 6 5 6
// 7 7 7 7 7 7 7 7 7
let imagePasses = [
{
// pass 1 - 1px
x: [0],
y: [0],
},
{
// pass 2 - 1px
x: [4],
y: [0],
},
{
// pass 3 - 2px
x: [0, 4],
y: [4],
},
{
// pass 4 - 4px
x: [2, 6],
y: [0, 4],
},
{
// pass 5 - 8px
x: [0, 2, 4, 6],
y: [2, 6],
},
{
// pass 6 - 16px
x: [1, 3, 5, 7],
y: [0, 2, 4, 6],
},
{
// pass 7 - 32px
x: [0, 1, 2, 3, 4, 5, 6, 7],
y: [1, 3, 5, 7],
},
];
exports.getImagePasses = function (width, height) {
let images = [];
let xLeftOver = width % 8;
let yLeftOver = height % 8;
let xRepeats = (width - xLeftOver) / 8;
let yRepeats = (height - yLeftOver) / 8;
for (let i = 0; i < imagePasses.length; i++) {
let pass = imagePasses[i];
let passWidth = xRepeats * pass.x.length;
let passHeight = yRepeats * pass.y.length;
for (let j = 0; j < pass.x.length; j++) {
if (pass.x[j] < xLeftOver) {
passWidth++;
} else {
break;
}
}
for (let j = 0; j < pass.y.length; j++) {
if (pass.y[j] < yLeftOver) {
passHeight++;
} else {
break;
}
}
if (passWidth > 0 && passHeight > 0) {
images.push({ width: passWidth, height: passHeight, index: i });
}
}
return images;
};
exports.getInterlaceIterator = function (width) {
return function (x, y, pass) {
let outerXLeftOver = x % imagePasses[pass].x.length;
let outerX =
((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 +
imagePasses[pass].x[outerXLeftOver];
let outerYLeftOver = y % imagePasses[pass].y.length;
let outerY =
((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 +
imagePasses[pass].y[outerYLeftOver];
return outerX * 4 + outerY * width * 4;
};
};
@@ -0,0 +1,50 @@
"use strict";
let util = require("util");
let Stream = require("stream");
let constants = require("./constants");
let Packer = require("./packer");
let PackerAsync = (module.exports = function (opt) {
Stream.call(this);
let options = opt || {};
this._packer = new Packer(options);
this._deflate = this._packer.createDeflate();
this.readable = true;
});
util.inherits(PackerAsync, Stream);
PackerAsync.prototype.pack = function (data, width, height, gamma) {
// Signature
this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
this.emit("data", this._packer.packIHDR(width, height));
if (gamma) {
this.emit("data", this._packer.packGAMA(gamma));
}
let filteredData = this._packer.filterData(data, width, height);
// compress it
this._deflate.on("error", this.emit.bind(this, "error"));
this._deflate.on(
"data",
function (compressedData) {
this.emit("data", this._packer.packIDAT(compressedData));
}.bind(this)
);
this._deflate.on(
"end",
function () {
this.emit("data", this._packer.packIEND());
this.emit("end");
}.bind(this)
);
this._deflate.end(filteredData);
};
@@ -0,0 +1,56 @@
"use strict";
let hasSyncZlib = true;
let zlib = require("zlib");
if (!zlib.deflateSync) {
hasSyncZlib = false;
}
let constants = require("./constants");
let Packer = require("./packer");
module.exports = function (metaData, opt) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let options = opt || {};
let packer = new Packer(options);
let chunks = [];
// Signature
chunks.push(Buffer.from(constants.PNG_SIGNATURE));
// Header
chunks.push(packer.packIHDR(metaData.width, metaData.height));
if (metaData.gamma) {
chunks.push(packer.packGAMA(metaData.gamma));
}
let filteredData = packer.filterData(
metaData.data,
metaData.width,
metaData.height
);
// compress it
let compressedData = zlib.deflateSync(
filteredData,
packer.getDeflateOptions()
);
filteredData = null;
if (!compressedData || !compressedData.length) {
throw new Error("bad png - invalid compressed data response");
}
chunks.push(packer.packIDAT(compressedData));
// End
chunks.push(packer.packIEND());
return Buffer.concat(chunks);
};
@@ -0,0 +1,129 @@
"use strict";
let constants = require("./constants");
let CrcStream = require("./crc");
let bitPacker = require("./bitpacker");
let filter = require("./filter-pack");
let zlib = require("zlib");
let Packer = (module.exports = function (options) {
this._options = options;
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
options.deflateLevel =
options.deflateLevel != null ? options.deflateLevel : 9;
options.deflateStrategy =
options.deflateStrategy != null ? options.deflateStrategy : 3;
options.inputHasAlpha =
options.inputHasAlpha != null ? options.inputHasAlpha : true;
options.deflateFactory = options.deflateFactory || zlib.createDeflate;
options.bitDepth = options.bitDepth || 8;
// This is outputColorType
options.colorType =
typeof options.colorType === "number"
? options.colorType
: constants.COLORTYPE_COLOR_ALPHA;
options.inputColorType =
typeof options.inputColorType === "number"
? options.inputColorType
: constants.COLORTYPE_COLOR_ALPHA;
if (
[
constants.COLORTYPE_GRAYSCALE,
constants.COLORTYPE_COLOR,
constants.COLORTYPE_COLOR_ALPHA,
constants.COLORTYPE_ALPHA,
].indexOf(options.colorType) === -1
) {
throw new Error(
"option color type:" + options.colorType + " is not supported at present"
);
}
if (
[
constants.COLORTYPE_GRAYSCALE,
constants.COLORTYPE_COLOR,
constants.COLORTYPE_COLOR_ALPHA,
constants.COLORTYPE_ALPHA,
].indexOf(options.inputColorType) === -1
) {
throw new Error(
"option input color type:" +
options.inputColorType +
" is not supported at present"
);
}
if (options.bitDepth !== 8 && options.bitDepth !== 16) {
throw new Error(
"option bit depth:" + options.bitDepth + " is not supported at present"
);
}
});
Packer.prototype.getDeflateOptions = function () {
return {
chunkSize: this._options.deflateChunkSize,
level: this._options.deflateLevel,
strategy: this._options.deflateStrategy,
};
};
Packer.prototype.createDeflate = function () {
return this._options.deflateFactory(this.getDeflateOptions());
};
Packer.prototype.filterData = function (data, width, height) {
// convert to correct format for filtering (e.g. right bpp and bit depth)
let packedData = bitPacker(data, width, height, this._options);
// filter pixel data
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType];
let filteredData = filter(packedData, width, height, this._options, bpp);
return filteredData;
};
Packer.prototype._packChunk = function (type, data) {
let len = data ? data.length : 0;
let buf = Buffer.alloc(len + 12);
buf.writeUInt32BE(len, 0);
buf.writeUInt32BE(type, 4);
if (data) {
data.copy(buf, 8);
}
buf.writeInt32BE(
CrcStream.crc32(buf.slice(4, buf.length - 4)),
buf.length - 4
);
return buf;
};
Packer.prototype.packGAMA = function (gamma) {
let buf = Buffer.alloc(4);
buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0);
return this._packChunk(constants.TYPE_gAMA, buf);
};
Packer.prototype.packIHDR = function (width, height) {
let buf = Buffer.alloc(13);
buf.writeUInt32BE(width, 0);
buf.writeUInt32BE(height, 4);
buf[8] = this._options.bitDepth; // Bit depth
buf[9] = this._options.colorType; // colorType
buf[10] = 0; // compression
buf[11] = 0; // filter
buf[12] = 0; // interlace
return this._packChunk(constants.TYPE_IHDR, buf);
};
Packer.prototype.packIDAT = function (data) {
return this._packChunk(constants.TYPE_IDAT, data);
};
Packer.prototype.packIEND = function () {
return this._packChunk(constants.TYPE_IEND, null);
};
@@ -0,0 +1,16 @@
"use strict";
module.exports = function paethPredictor(left, above, upLeft) {
let paeth = left + above - upLeft;
let pLeft = Math.abs(paeth - left);
let pAbove = Math.abs(paeth - above);
let pUpLeft = Math.abs(paeth - upLeft);
if (pLeft <= pAbove && pLeft <= pUpLeft) {
return left;
}
if (pAbove <= pUpLeft) {
return above;
}
return upLeft;
};
@@ -0,0 +1,169 @@
"use strict";
let util = require("util");
let zlib = require("zlib");
let ChunkStream = require("./chunkstream");
let FilterAsync = require("./filter-parse-async");
let Parser = require("./parser");
let bitmapper = require("./bitmapper");
let formatNormaliser = require("./format-normaliser");
let ParserAsync = (module.exports = function (options) {
ChunkStream.call(this);
this._parser = new Parser(options, {
read: this.read.bind(this),
error: this._handleError.bind(this),
metadata: this._handleMetaData.bind(this),
gamma: this.emit.bind(this, "gamma"),
palette: this._handlePalette.bind(this),
transColor: this._handleTransColor.bind(this),
finished: this._finished.bind(this),
inflateData: this._inflateData.bind(this),
simpleTransparency: this._simpleTransparency.bind(this),
headersFinished: this._headersFinished.bind(this),
});
this._options = options;
this.writable = true;
this._parser.start();
});
util.inherits(ParserAsync, ChunkStream);
ParserAsync.prototype._handleError = function (err) {
this.emit("error", err);
this.writable = false;
this.destroy();
if (this._inflate && this._inflate.destroy) {
this._inflate.destroy();
}
if (this._filter) {
this._filter.destroy();
// For backward compatibility with Node 7 and below.
// Suppress errors due to _inflate calling write() even after
// it's destroy()'ed.
this._filter.on("error", function () {});
}
this.errord = true;
};
ParserAsync.prototype._inflateData = function (data) {
if (!this._inflate) {
if (this._bitmapInfo.interlace) {
this._inflate = zlib.createInflate();
this._inflate.on("error", this.emit.bind(this, "error"));
this._filter.on("complete", this._complete.bind(this));
this._inflate.pipe(this._filter);
} else {
let rowSize =
((this._bitmapInfo.width *
this._bitmapInfo.bpp *
this._bitmapInfo.depth +
7) >>
3) +
1;
let imageSize = rowSize * this._bitmapInfo.height;
let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK);
this._inflate = zlib.createInflate({ chunkSize: chunkSize });
let leftToInflate = imageSize;
let emitError = this.emit.bind(this, "error");
this._inflate.on("error", function (err) {
if (!leftToInflate) {
return;
}
emitError(err);
});
this._filter.on("complete", this._complete.bind(this));
let filterWrite = this._filter.write.bind(this._filter);
this._inflate.on("data", function (chunk) {
if (!leftToInflate) {
return;
}
if (chunk.length > leftToInflate) {
chunk = chunk.slice(0, leftToInflate);
}
leftToInflate -= chunk.length;
filterWrite(chunk);
});
this._inflate.on("end", this._filter.end.bind(this._filter));
}
}
this._inflate.write(data);
};
ParserAsync.prototype._handleMetaData = function (metaData) {
this._metaData = metaData;
this._bitmapInfo = Object.create(metaData);
this._filter = new FilterAsync(this._bitmapInfo);
};
ParserAsync.prototype._handleTransColor = function (transColor) {
this._bitmapInfo.transColor = transColor;
};
ParserAsync.prototype._handlePalette = function (palette) {
this._bitmapInfo.palette = palette;
};
ParserAsync.prototype._simpleTransparency = function () {
this._metaData.alpha = true;
};
ParserAsync.prototype._headersFinished = function () {
// Up until this point, we don't know if we have a tRNS chunk (alpha)
// so we can't emit metadata any earlier
this.emit("metadata", this._metaData);
};
ParserAsync.prototype._finished = function () {
if (this.errord) {
return;
}
if (!this._inflate) {
this.emit("error", "No Inflate block");
} else {
// no more data to inflate
this._inflate.end();
}
};
ParserAsync.prototype._complete = function (filteredData) {
if (this.errord) {
return;
}
let normalisedBitmapData;
try {
let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo);
normalisedBitmapData = formatNormaliser(
bitmapData,
this._bitmapInfo,
this._options.skipRescale
);
bitmapData = null;
} catch (ex) {
this._handleError(ex);
return;
}
this.emit("parsed", normalisedBitmapData);
};
@@ -0,0 +1,112 @@
"use strict";
let hasSyncZlib = true;
let zlib = require("zlib");
let inflateSync = require("./sync-inflate");
if (!zlib.deflateSync) {
hasSyncZlib = false;
}
let SyncReader = require("./sync-reader");
let FilterSync = require("./filter-parse-sync");
let Parser = require("./parser");
let bitmapper = require("./bitmapper");
let formatNormaliser = require("./format-normaliser");
module.exports = function (buffer, options) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let err;
function handleError(_err_) {
err = _err_;
}
let metaData;
function handleMetaData(_metaData_) {
metaData = _metaData_;
}
function handleTransColor(transColor) {
metaData.transColor = transColor;
}
function handlePalette(palette) {
metaData.palette = palette;
}
function handleSimpleTransparency() {
metaData.alpha = true;
}
let gamma;
function handleGamma(_gamma_) {
gamma = _gamma_;
}
let inflateDataList = [];
function handleInflateData(inflatedData) {
inflateDataList.push(inflatedData);
}
let reader = new SyncReader(buffer);
let parser = new Parser(options, {
read: reader.read.bind(reader),
error: handleError,
metadata: handleMetaData,
gamma: handleGamma,
palette: handlePalette,
transColor: handleTransColor,
inflateData: handleInflateData,
simpleTransparency: handleSimpleTransparency,
});
parser.start();
reader.process();
if (err) {
throw err;
}
//join together the inflate datas
let inflateData = Buffer.concat(inflateDataList);
inflateDataList.length = 0;
let inflatedData;
if (metaData.interlace) {
inflatedData = zlib.inflateSync(inflateData);
} else {
let rowSize =
((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1;
let imageSize = rowSize * metaData.height;
inflatedData = inflateSync(inflateData, {
chunkSize: imageSize,
maxLength: imageSize,
});
}
inflateData = null;
if (!inflatedData || !inflatedData.length) {
throw new Error("bad png - invalid inflate data response");
}
let unfilteredData = FilterSync.process(inflatedData, metaData);
inflateData = null;
let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData);
unfilteredData = null;
let normalisedBitmapData = formatNormaliser(
bitmapData,
metaData,
options.skipRescale
);
metaData.data = normalisedBitmapData;
metaData.gamma = gamma || 0;
return metaData;
};
@@ -0,0 +1,295 @@
"use strict";
let constants = require("./constants");
let CrcCalculator = require("./crc");
let Parser = (module.exports = function (options, dependencies) {
this._options = options;
options.checkCRC = options.checkCRC !== false;
this._hasIHDR = false;
this._hasIEND = false;
this._emittedHeadersFinished = false;
// input flags/metadata
this._palette = [];
this._colorType = 0;
this._chunks = {};
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
this.read = dependencies.read;
this.error = dependencies.error;
this.metadata = dependencies.metadata;
this.gamma = dependencies.gamma;
this.transColor = dependencies.transColor;
this.palette = dependencies.palette;
this.parsed = dependencies.parsed;
this.inflateData = dependencies.inflateData;
this.finished = dependencies.finished;
this.simpleTransparency = dependencies.simpleTransparency;
this.headersFinished = dependencies.headersFinished || function () {};
});
Parser.prototype.start = function () {
this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
};
Parser.prototype._parseSignature = function (data) {
let signature = constants.PNG_SIGNATURE;
for (let i = 0; i < signature.length; i++) {
if (data[i] !== signature[i]) {
this.error(new Error("Invalid file signature"));
return;
}
}
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._parseChunkBegin = function (data) {
// chunk content length
let length = data.readUInt32BE(0);
// chunk type
let type = data.readUInt32BE(4);
let name = "";
for (let i = 4; i < 8; i++) {
const charCode = data[i];
if (charCode < 65 || charCode > 122 || (charCode > 90 && charCode < 97)) {
this.error(new Error("Invalid chunk type"));
return;
}
name += String.fromCharCode(charCode);
}
//console.log('chunk ', name, length);
// chunk flags
let ancillary = Boolean(data[4] & 0x20); // or critical
// priv = Boolean(data[5] & 0x20), // or public
// safeToCopy = Boolean(data[7] & 0x20); // or unsafe
if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
this.error(new Error("Expected IHDR to be first chunk"));
return;
}
this._crc = new CrcCalculator();
this._crc.write(Buffer.from(name));
if (this._chunks[type]) {
return this._chunks[type](length);
}
if (!ancillary) {
this.error(new Error("Unsupported critical chunk type " + name));
return;
}
this.read(length + 4, this._skipChunk.bind(this));
};
Parser.prototype._skipChunk = function (/*data*/) {
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._handleChunkEnd = function () {
this.read(4, this._parseChunkEnd.bind(this));
};
Parser.prototype._parseChunkEnd = function (data) {
let fileCrc = data.readInt32BE(0);
let calcCrc = this._crc.crc32();
// check CRC
if (this._options.checkCRC && calcCrc !== fileCrc) {
this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc));
return;
}
if (!this._hasIEND) {
this.read(8, this._parseChunkBegin.bind(this));
}
};
Parser.prototype._handleIHDR = function (length) {
this.read(length, this._parseIHDR.bind(this));
};
Parser.prototype._parseIHDR = function (data) {
this._crc.write(data);
let width = data.readUInt32BE(0);
let height = data.readUInt32BE(4);
let depth = data[8];
let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha
let compr = data[10];
let filter = data[11];
let interlace = data[12];
// console.log(' width', width, 'height', height,
// 'depth', depth, 'colorType', colorType,
// 'compr', compr, 'filter', filter, 'interlace', interlace
// );
if (
depth !== 8 &&
depth !== 4 &&
depth !== 2 &&
depth !== 1 &&
depth !== 16
) {
this.error(new Error("Unsupported bit depth " + depth));
return;
}
if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
this.error(new Error("Unsupported color type"));
return;
}
if (compr !== 0) {
this.error(new Error("Unsupported compression method"));
return;
}
if (filter !== 0) {
this.error(new Error("Unsupported filter method"));
return;
}
if (interlace !== 0 && interlace !== 1) {
this.error(new Error("Unsupported interlace method"));
return;
}
this._colorType = colorType;
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
this._hasIHDR = true;
this.metadata({
width: width,
height: height,
depth: depth,
interlace: Boolean(interlace),
palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
color: Boolean(colorType & constants.COLORTYPE_COLOR),
alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
bpp: bpp,
colorType: colorType,
});
this._handleChunkEnd();
};
Parser.prototype._handlePLTE = function (length) {
this.read(length, this._parsePLTE.bind(this));
};
Parser.prototype._parsePLTE = function (data) {
this._crc.write(data);
let entries = Math.floor(data.length / 3);
// console.log('Palette:', entries);
for (let i = 0; i < entries; i++) {
this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]);
}
this.palette(this._palette);
this._handleChunkEnd();
};
Parser.prototype._handleTRNS = function (length) {
this.simpleTransparency();
this.read(length, this._parseTRNS.bind(this));
};
Parser.prototype._parseTRNS = function (data) {
this._crc.write(data);
// palette
if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
if (this._palette.length === 0) {
this.error(new Error("Transparency chunk must be after palette"));
return;
}
if (data.length > this._palette.length) {
this.error(new Error("More transparent colors than palette size"));
return;
}
for (let i = 0; i < data.length; i++) {
this._palette[i][3] = data[i];
}
this.palette(this._palette);
}
// for colorType 0 (grayscale) and 2 (rgb)
// there might be one gray/color defined as transparent
if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
// grey, 2 bytes
this.transColor([data.readUInt16BE(0)]);
}
if (this._colorType === constants.COLORTYPE_COLOR) {
this.transColor([
data.readUInt16BE(0),
data.readUInt16BE(2),
data.readUInt16BE(4),
]);
}
this._handleChunkEnd();
};
Parser.prototype._handleGAMA = function (length) {
this.read(length, this._parseGAMA.bind(this));
};
Parser.prototype._parseGAMA = function (data) {
this._crc.write(data);
this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
this._handleChunkEnd();
};
Parser.prototype._handleIDAT = function (length) {
if (!this._emittedHeadersFinished) {
this._emittedHeadersFinished = true;
this.headersFinished();
}
this.read(-length, this._parseIDAT.bind(this, length));
};
Parser.prototype._parseIDAT = function (length, data) {
this._crc.write(data);
if (
this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
this._palette.length === 0
) {
throw new Error("Expected palette not found");
}
this.inflateData(data);
let leftOverLength = length - data.length;
if (leftOverLength > 0) {
this._handleIDAT(leftOverLength);
} else {
this._handleChunkEnd();
}
};
Parser.prototype._handleIEND = function (length) {
this.read(length, this._parseIEND.bind(this));
};
Parser.prototype._parseIEND = function (data) {
this._crc.write(data);
this._hasIEND = true;
this._handleChunkEnd();
if (this.finished) {
this.finished();
}
};
@@ -0,0 +1,12 @@
"use strict";
let parse = require("./parser-sync");
let pack = require("./packer-sync");
exports.read = function (buffer, options) {
return parse(buffer, options || {});
};
exports.write = function (png, options) {
return pack(png, options);
};
+194
View File
@@ -0,0 +1,194 @@
"use strict";
let util = require("util");
let Stream = require("stream");
let Parser = require("./parser-async");
let Packer = require("./packer-async");
let PNGSync = require("./png-sync");
let PNG = (exports.PNG = function (options) {
Stream.call(this);
options = options || {}; // eslint-disable-line no-param-reassign
// coerce pixel dimensions to integers (also coerces undefined -> 0):
this.width = options.width | 0;
this.height = options.height | 0;
this.data =
this.width > 0 && this.height > 0
? Buffer.alloc(4 * this.width * this.height)
: null;
if (options.fill && this.data) {
this.data.fill(0);
}
this.gamma = 0;
this.readable = this.writable = true;
this._parser = new Parser(options);
this._parser.on("error", this.emit.bind(this, "error"));
this._parser.on("close", this._handleClose.bind(this));
this._parser.on("metadata", this._metadata.bind(this));
this._parser.on("gamma", this._gamma.bind(this));
this._parser.on(
"parsed",
function (data) {
this.data = data;
this.emit("parsed", data);
}.bind(this)
);
this._packer = new Packer(options);
this._packer.on("data", this.emit.bind(this, "data"));
this._packer.on("end", this.emit.bind(this, "end"));
this._parser.on("close", this._handleClose.bind(this));
this._packer.on("error", this.emit.bind(this, "error"));
});
util.inherits(PNG, Stream);
PNG.sync = PNGSync;
PNG.prototype.pack = function () {
if (!this.data || !this.data.length) {
this.emit("error", "No data provided");
return this;
}
process.nextTick(
function () {
this._packer.pack(this.data, this.width, this.height, this.gamma);
}.bind(this)
);
return this;
};
PNG.prototype.parse = function (data, callback) {
if (callback) {
let onParsed, onError;
onParsed = function (parsedData) {
this.removeListener("error", onError);
this.data = parsedData;
callback(null, this);
}.bind(this);
onError = function (err) {
this.removeListener("parsed", onParsed);
callback(err, null);
}.bind(this);
this.once("parsed", onParsed);
this.once("error", onError);
}
this.end(data);
return this;
};
PNG.prototype.write = function (data) {
this._parser.write(data);
return true;
};
PNG.prototype.end = function (data) {
this._parser.end(data);
};
PNG.prototype._metadata = function (metadata) {
this.width = metadata.width;
this.height = metadata.height;
this.emit("metadata", metadata);
};
PNG.prototype._gamma = function (gamma) {
this.gamma = gamma;
};
PNG.prototype._handleClose = function () {
if (!this._parser.writable && !this._packer.readable) {
this.emit("close");
}
};
PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) {
// eslint-disable-line max-params
// coerce pixel dimensions to integers (also coerces undefined -> 0):
/* eslint-disable no-param-reassign */
srcX |= 0;
srcY |= 0;
width |= 0;
height |= 0;
deltaX |= 0;
deltaY |= 0;
/* eslint-enable no-param-reassign */
if (
srcX > src.width ||
srcY > src.height ||
srcX + width > src.width ||
srcY + height > src.height
) {
throw new Error("bitblt reading outside image");
}
if (
deltaX > dst.width ||
deltaY > dst.height ||
deltaX + width > dst.width ||
deltaY + height > dst.height
) {
throw new Error("bitblt writing outside image");
}
for (let y = 0; y < height; y++) {
src.data.copy(
dst.data,
((deltaY + y) * dst.width + deltaX) << 2,
((srcY + y) * src.width + srcX) << 2,
((srcY + y) * src.width + srcX + width) << 2
);
}
};
PNG.prototype.bitblt = function (
dst,
srcX,
srcY,
width,
height,
deltaX,
deltaY
) {
// eslint-disable-line max-params
PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY);
return this;
};
PNG.adjustGamma = function (src) {
if (src.gamma) {
for (let y = 0; y < src.height; y++) {
for (let x = 0; x < src.width; x++) {
let idx = (src.width * y + x) << 2;
for (let i = 0; i < 3; i++) {
let sample = src.data[idx + i] / 255;
sample = Math.pow(sample, 1 / 2.2 / src.gamma);
src.data[idx + i] = Math.round(sample * 255);
}
}
}
src.gamma = 0;
}
};
PNG.prototype.adjustGamma = function () {
PNG.adjustGamma(this);
};
@@ -0,0 +1,168 @@
"use strict";
let assert = require("assert").ok;
let zlib = require("zlib");
let util = require("util");
let kMaxLength = require("buffer").kMaxLength;
function Inflate(opts) {
if (!(this instanceof Inflate)) {
return new Inflate(opts);
}
if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) {
opts.chunkSize = zlib.Z_MIN_CHUNK;
}
zlib.Inflate.call(this, opts);
// Node 8 --> 9 compatibility check
this._offset = this._offset === undefined ? this._outOffset : this._offset;
this._buffer = this._buffer || this._outBuffer;
if (opts && opts.maxLength != null) {
this._maxLength = opts.maxLength;
}
}
function createInflate(opts) {
return new Inflate(opts);
}
function _close(engine, callback) {
if (callback) {
process.nextTick(callback);
}
// Caller may invoke .close after a zlib error (which will null _handle).
if (!engine._handle) {
return;
}
engine._handle.close();
engine._handle = null;
}
Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) {
if (typeof asyncCb === "function") {
return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb);
}
let self = this;
let availInBefore = chunk && chunk.length;
let availOutBefore = this._chunkSize - this._offset;
let leftToInflate = this._maxLength;
let inOff = 0;
let buffers = [];
let nread = 0;
let error;
this.on("error", function (err) {
error = err;
});
function handleChunk(availInAfter, availOutAfter) {
if (self._hadError) {
return;
}
let have = availOutBefore - availOutAfter;
assert(have >= 0, "have should not go down");
if (have > 0) {
let out = self._buffer.slice(self._offset, self._offset + have);
self._offset += have;
if (out.length > leftToInflate) {
out = out.slice(0, leftToInflate);
}
buffers.push(out);
nread += out.length;
leftToInflate -= out.length;
if (leftToInflate === 0) {
return false;
}
}
if (availOutAfter === 0 || self._offset >= self._chunkSize) {
availOutBefore = self._chunkSize;
self._offset = 0;
self._buffer = Buffer.allocUnsafe(self._chunkSize);
}
if (availOutAfter === 0) {
inOff += availInBefore - availInAfter;
availInBefore = availInAfter;
return true;
}
return false;
}
assert(this._handle, "zlib binding closed");
let res;
do {
res = this._handle.writeSync(
flushFlag,
chunk, // in
inOff, // in_off
availInBefore, // in_len
this._buffer, // out
this._offset, //out_off
availOutBefore
); // out_len
// Node 8 --> 9 compatibility check
res = res || this._writeState;
} while (!this._hadError && handleChunk(res[0], res[1]));
if (this._hadError) {
throw error;
}
if (nread >= kMaxLength) {
_close(this);
throw new RangeError(
"Cannot create final Buffer. It would be larger than 0x" +
kMaxLength.toString(16) +
" bytes"
);
}
let buf = Buffer.concat(buffers, nread);
_close(this);
return buf;
};
util.inherits(Inflate, zlib.Inflate);
function zlibBufferSync(engine, buffer) {
if (typeof buffer === "string") {
buffer = Buffer.from(buffer);
}
if (!(buffer instanceof Buffer)) {
throw new TypeError("Not a string or buffer");
}
let flushFlag = engine._finishFlushFlag;
if (flushFlag == null) {
flushFlag = zlib.Z_FINISH;
}
return engine._processChunk(buffer, flushFlag);
}
function inflateSync(buffer, opts) {
return zlibBufferSync(new Inflate(opts), buffer);
}
module.exports = exports = inflateSync;
exports.Inflate = Inflate;
exports.createInflate = createInflate;
exports.inflateSync = inflateSync;
@@ -0,0 +1,45 @@
"use strict";
let SyncReader = (module.exports = function (buffer) {
this._buffer = buffer;
this._reads = [];
});
SyncReader.prototype.read = function (length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback,
});
};
SyncReader.prototype.process = function () {
// as long as there is any data and read requests
while (this._reads.length > 0 && this._buffer.length) {
let read = this._reads[0];
if (
this._buffer.length &&
(this._buffer.length >= read.length || read.allowLess)
) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
let buf = this._buffer;
this._buffer = buf.slice(read.length);
read.func.call(this, buf.slice(0, read.length));
} else {
break;
}
}
if (this._reads.length > 0) {
throw new Error("There are some read requests waiting on finished stream");
}
if (this._buffer.length > 0) {
throw new Error("Unrecognised content at end of stream");
}
};
+1 -1
View File
@@ -239,7 +239,7 @@ $(document).ready(function() {
if(foundCourses.length > 0) {
$('[name="skilltitle"]').append($('<option>', {
value: json["skilltitle"][i].id,
text: json["skilltitle"][i].name,
text: json["skilltitle"][i].name + ' (' + json["skilltitle"][i].info + ')',
}));
}
}
@@ -18,7 +18,7 @@ $(document).ready(async function() {
function(response){
document.getElementById("logtextarea").textContent = 'Done.\n\n'
document.getElementById("logtextarea").textContent += 'NOTE:\n- labels for these new files (nemsys, bgm, submonitor bg, chat stamps) in the asset/json/data.json file needs to be updated.\n'
document.getElementById("logtextarea").textContent += '- for converting s3p files to mp3, check guide in the notes section above.\n\n\n\n'
document.getElementById("logtextarea").textContent += '- for converting s3p files to mp3, check guide in the notes section above.\n\n'
if(response['data']['errors'].length > 0) {
document.getElementById("logtextarea").textContent += 'Errors: \n'
@@ -28,6 +28,19 @@ $(document).ready(async function() {
document.getElementById("logtextarea").textContent += '\n\n'
}
if(response['data']['course']) {
document.getElementById("logtextarea").textContent += "Updated course_data.json from data/exg.ts!"
}
document.getElementById("logtextarea").textContent += '\n\n\n'
if(response['data']['ifs'].length > 0) {
document.getElementById("logtextarea").textContent += 'Successfully extracted textures: \n'
$.each(response['data']['ifs'], function(key, val) {
document.getElementById("logtextarea").textContent += "- " + val + '\n'
})
document.getElementById("logtextarea").textContent += '\n\n'
}
if(response['data']['versionSongs'].length > 0) {
document.getElementById("logtextarea").textContent += 'New songs in latest version data: \n'
$.each(response['data']['versionSongs'], function(key, val) {
@@ -103,7 +116,6 @@ $(document).ready(async function() {
document.getElementById("logtextarea").textContent += '\n\n'
}
// console.log(JSON.stringify(data))
if(response['data']['akaname'].length > 0) {
document.getElementById("logtextarea").textContent += 'New akanames: \n'
$.each(response['data']['akaname'], function(key, val) {
@@ -58,79 +58,98 @@
"skilltitle": [
{
"id": 1,
"name": "岳翔 (Lv01)"
"name": "岳翔",
"info": "Lv01"
},
{
"id": 2,
"name": "流星 (Lv02)"
"name": "流星",
"info": "Lv02"
},
{
"id": 3,
"name": "月衝 (Lv03)"
"name": "月衝",
"info": "Lv03"
},
{
"id": 4,
"name": "瞬光 (Lv04)"
"name": "瞬光",
"info": "Lv04"
},
{
"id": 5,
"name": "天極 (Lv05)"
"name": "天極",
"info": "Lv05"
},
{
"id": 6,
"name": "烈風 (Lv06)"
"name": "烈風",
"info": "Lv06"
},
{
"id": 7,
"name": "雷電 (Lv07)"
"name": "雷電",
"info": "Lv07"
},
{
"id": 8,
"name": "麗華 (Lv08)"
"name": "麗華",
"info": "Lv08"
},
{
"id": 9,
"name": "魔騎士 (Lv09)"
"name": "魔騎士",
"info": "Lv09"
},
{
"id": 10,
"name": "剛力羅 (Lv10)"
"name": "剛力羅",
"info": "Lv10"
},
{
"id": 11,
"name": "或帝滅斗 (Lv11)"
"name": "或帝滅斗",
"info": "Lv11"
},
{
"id": 12,
"name": "暴龍天 (Lv∞)"
"name": "暴龍天",
"info": "Lv∞"
},
{
"id": 13,
"name": "BMK2021 (BEMANI MASTER KOREA 2021)"
"name": "BMK2021",
"info": "BEMANI MASTER KOREA 2021"
},
{
"id": 14,
"name": "天晴 (10th Anniversary)"
"name": "天晴",
"info": "10th Anniversary"
},
{
"id": 15,
"name": "詠鰤琉 (New Life Support)"
"name": "詠鰤琉",
"info": "New Life Support"
},
{
"id": 16,
"name": "Red Bull (Red Bull 5G 2022)"
"name": "Red Bull",
"info": "Red Bull 5G 2022"
},
{
"id": 17,
"name": "U.S.O. (Spring Volte Festival 2023)"
"name": "U.S.O.",
"info": "Spring Volte Festival 2023"
},
{
"id": 18,
"name": "完喰 (KAC 2023 Challenge Round A)"
"name": "完喰",
"info": "KAC 2023 Challenge Round A"
},
{
"id": 19,
"name": "夢吹 (KAC 2023 Challenge Round B)"
"name": "夢吹",
"info": "KAC 2023 Challenge Round B"
}
]
}
@@ -3,6 +3,7 @@
{
"id": 1,
"name": "Premium Generator(この素晴らしい世界に祝福を!)",
"jpn_exc": true,
"items": {
"crew": [131],
"stamp": [69, 70, 71, 72, 73, 74, 75, 76, 77, 78],
@@ -12,6 +13,7 @@
{
"id": 2,
"name": "Premium Generator Vol. 1",
"jpn_exc": false,
"items": {
"crew": [134],
"stamp": [110, 111, 112, 113, 114, 115, 116, 117, 118, 119],
@@ -21,6 +23,7 @@
{
"id": 3,
"name": "Premium Generator Vol. 2",
"jpn_exc": false,
"items": {
"crew": [136, 137, 138],
"stamp": [151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162],
@@ -30,6 +33,7 @@
{
"id": 4,
"name": "Premium Generator (Re:ゼロから始める異世界生活)前半",
"jpn_exc": true,
"items": {
"crew": [139],
"stamp": [194, 195, 196, 197, 198, 199],
@@ -39,6 +43,7 @@
{
"id": 5,
"name": "Premium Generator (Re:ゼロから始める異世界生活)後半",
"jpn_exc": true,
"items": {
"crew": [140],
"stamp": [200, 201, 202, 203, 204, 205],
@@ -48,6 +53,7 @@
{
"id": 6,
"name": "Premium Generator Vol. 3",
"jpn_exc": false,
"items": {
"crew": [142],
"stamp": [239, 240, 241, 242, 243, 244, 245, 246, 247, 248],
@@ -58,6 +64,7 @@
{
"id": 7,
"name": "Premium Generator(白上フブキ)",
"jpn_exc": true,
"items": {
"crew": [143],
"stamp": [249, 250, 251, 252, 253, 254, 255, 256, 257, 258],
+2
View File
@@ -2,6 +2,7 @@
profile: DB.FindOne(refid, { collection: 'profile' })
score_data: DB.Find(refid, { collection: 'music' })
skill_data: DB.Find(refid,{collection: 'skill'})
course_data: DB.Find(refid,{collection: 'course'})
arena_data: DB.Find(refid,{collection: 'arena'})
-
@@ -33,5 +34,6 @@ div
div(hidden id='data-pass') !{JSON.stringify(profile)}
div(hidden id='score-pass') !{JSON.stringify(score_data)}
div(hidden id='skill-pass') !{JSON.stringify(skill_data)}
div(hidden id='course-pass') !{JSON.stringify(course_data)}
div(hidden id='arena-pass') !{JSON.stringify(arena_data)}
script(src="static/asset/js/detail.js")
+8 -17
View File
@@ -1,27 +1,18 @@
div
.card
button.collapse() Notes
.card-info.collapsible-card
p.card-header-info
h5() Notes:
ul
li() For use with Exceed Gear arcade data.
li() Use this if you have arcade data that can be used. It wouldn't work otherwise.
li() Make sure you have your 'Exceed Gear Data Directory' in the plugin settings properly configured.
li() Update your datacode in the ea3-config.xml file.
br
p.card-header-bgm-guide
h5() BGM asset guide
p() This feature pulls BGM files from the data, however they are in the WMA file format, which is unsupported. For BGM preview to work, they have to be converted to MP3 format.
p() To convert these files to MP3, you can use ffmpeg. If you have ffmpeg already installed, there is a bgm_convert.bat script in the SDVX plugin directory that can automate the conversion to mp3.
ul
li() <a href="https://www.gyan.dev/ffmpeg/builds/">ffmpeg</a> - download and extract ffmpeg.exe and put it in the same directory as the bat script (or put ffmpeg.exe in PATH environment variable)
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| Update WebUI Assets (pull from game files)
.card-info.collapsible-card
ul
li(style="font-size:14px") To use this feature, configure 'Exceed Gear Data Directory' in the plugin settings.
li(style="font-size:14px") This will extract certain textures/images from .ifs files, however, currently it will only support IFS files used in the latest game version supported by this plugin.
li(style="font-size:14px") This feature pulls BGM files from the data, however they are in the WMA file format, which playback is unsupported so they have to be converted to MP3 format. To convert these files to MP3, you can use ffmpeg. If you have ffmpeg already installed, there are bgm_convert scripts in the SDVX plugin directory that can automate the conversion to mp3 (.bat for Windows, .sh for Linux)
ul
li(style="font-size:14px") Windows: <a href="https://www.gyan.dev/ffmpeg/builds/">download</a> and extract ffmpeg.exe and put it in the same directory as the bat script (or put ffmpeg.exe in PATH environment variable)
li(style="font-size:14px") Linux: follow <a href="https://json2video.com/how-to/ffmpeg-course/install-ffmpeg-linux.html">this guide</a> on how to install ffmpeg on your specific Linux distro.
.card-content
.field
button.button.is-primary(type="update" id="updateResources")