502 lines
12 KiB
JavaScript
502 lines
12 KiB
JavaScript
const supportDecoderApi = ('DecompressionStream' in self);
|
|
const isSWC = (head) => (head[0] === 0x43 && head[1] === 0x57 && head[2] === 0x53);
|
|
const outputSize = (head) => new DataView(head.buffer).getUint32(4, true);
|
|
|
|
function Decoder(size, offset = 8) {
|
|
if(!supportDecoderApi) {
|
|
throw 'Your browser not support DecompressionStream =(';
|
|
}
|
|
|
|
const decoder = new self.DecompressionStream('deflate');
|
|
const decoderW = decoder.writable.getWriter();
|
|
const decoderR = decoder.readable.getReader();
|
|
const buffer = new Uint8Array(size);
|
|
|
|
let isRunned = false;
|
|
let isDone = false;
|
|
|
|
let donableCallback;
|
|
|
|
function run() {
|
|
decoderR.read().then(function next(state) {
|
|
const done = state.done;
|
|
const value = state.value;
|
|
|
|
if (value) {
|
|
buffer.set(value, offset);
|
|
//console.debug("[Loader] Decoded chunk:", offset);
|
|
|
|
offset += value.length;
|
|
}
|
|
|
|
if (done || offset >= size) {
|
|
isDone = true;
|
|
|
|
if(donableCallback) {
|
|
donableCallback();
|
|
}
|
|
|
|
console.debug("[Loader] Decoder closed:", offset);
|
|
return;
|
|
}
|
|
|
|
return decoderR.read().then(next);
|
|
});
|
|
}
|
|
|
|
return {
|
|
get buffer() {
|
|
return buffer;
|
|
},
|
|
|
|
write(buffer) {
|
|
decoderW.ready.then(()=>{
|
|
decoderW.write(buffer);
|
|
|
|
if(!isRunned) {
|
|
isRunned = true;
|
|
run();
|
|
}
|
|
});
|
|
},
|
|
|
|
readAll() {
|
|
if(isDone) {
|
|
return Promise.resolve(buffer);
|
|
}
|
|
|
|
return new Promise((res)=>{
|
|
donableCallback = () => {
|
|
res(buffer);
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
function Fetcher(url = '', progress = f => f) {
|
|
let total = 0;
|
|
let reader;
|
|
let chunks = [];
|
|
|
|
progress && progress(0);
|
|
|
|
return fetch(url)
|
|
.then((request) => {
|
|
total = +request.headers.get('Content-Length');
|
|
reader = request.body.getReader();
|
|
|
|
return reader.read();
|
|
})
|
|
.then( (data) => {
|
|
const firstChunk = data.value;
|
|
|
|
console.debug("[Loader] Header:", String.fromCharCode.apply(null,firstChunk.slice(0, 3).reverse()));
|
|
|
|
let loaded = 0;
|
|
let decoder;
|
|
|
|
if (supportDecoderApi && isSWC(firstChunk)) {
|
|
const totalDecodedSize = outputSize(firstChunk);
|
|
const swcHeader = firstChunk.slice(0, 8);
|
|
|
|
swcHeader[0] = 70; // SWC => SWF
|
|
|
|
console.debug("[Loader] SWC size:", outputSize(firstChunk));
|
|
|
|
decoder = Decoder(totalDecodedSize, 8);
|
|
decoder.buffer.set(swcHeader);
|
|
|
|
// push witout header
|
|
decoder.write(firstChunk.slice(8));
|
|
|
|
} else {
|
|
chunks.push(firstChunk);
|
|
}
|
|
|
|
loaded += firstChunk.length;
|
|
|
|
progress && progress( Math.min(1, loaded / total));
|
|
|
|
// update all other chunks reqursive while !done
|
|
return reader.read().then( function moveNext(state) {
|
|
|
|
if (state.done) {
|
|
if(!decoder) {
|
|
let buffer = new Uint8Array(loaded);
|
|
let offset = 0;
|
|
|
|
chunks.forEach((e) => {
|
|
buffer.set(e, offset);
|
|
offset += e.length;
|
|
});
|
|
|
|
return buffer;
|
|
}else {
|
|
return decoder.readAll();
|
|
}
|
|
}
|
|
|
|
const value = state.value;
|
|
|
|
loaded += value.length;
|
|
progress && progress( Math.min(1, loaded / total));
|
|
|
|
if (!decoder) {
|
|
chunks.push(value);
|
|
} else {
|
|
decoder.write(value);
|
|
}
|
|
|
|
return reader.read().then(moveNext);
|
|
});
|
|
})
|
|
}
|
|
|
|
var Loader = (function () {
|
|
function loadBinary(file, progressEvent = f => f) {
|
|
const isScript = file.path.indexOf(".js") > -1;
|
|
|
|
if (!isScript && supportDecoderApi ) {
|
|
return Fetcher(file.path, progressEvent)
|
|
.then((buffer)=>({
|
|
meta: file.meta || {},
|
|
name: file.name,
|
|
path: file.path,
|
|
resourceType: file.resourceType,
|
|
data: buffer.buffer,
|
|
type: "swf",
|
|
}));
|
|
}
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
req.addEventListener("progress", e => {
|
|
const gzip = req.getAllResponseHeaders('content-encoding') === 'gzip';
|
|
|
|
// get from progress, then from request, and if not valid - from file
|
|
const total = e.total || (+req.getAllResponseHeaders('content-length')) || (file.size * (gzip ? 0.25 : 1));
|
|
|
|
if(!total) {
|
|
progressEvent(1);
|
|
return;
|
|
}
|
|
|
|
progressEvent(Math.min(1, e.loaded / total) );
|
|
});
|
|
|
|
req.open("GET", file.path, true);
|
|
req.responseType = isScript ? "text" : "arraybuffer";
|
|
|
|
return new Promise((res, rej) => {
|
|
req.addEventListener("error", rej);
|
|
req.addEventListener("load", () => {
|
|
progressEvent(1);
|
|
|
|
if (isScript) {
|
|
// unsafe
|
|
//eval(req.response);
|
|
|
|
const b = new Blob([req.response], { type: "text/javascript" });
|
|
// use blob
|
|
loadScript(URL.createObjectURL(b)).then(() => res(undefined));
|
|
|
|
return;
|
|
}
|
|
res({
|
|
meta: file.meta || {},
|
|
name: file.name,
|
|
path: file.path,
|
|
resourceType: file.resourceType,
|
|
data: req.response,
|
|
type: isScript ? "js" : "swf",
|
|
});
|
|
});
|
|
|
|
req.send();
|
|
});
|
|
}
|
|
|
|
function loadScript(file, progress) {
|
|
const head = document.querySelector("head");
|
|
const script = document.createElement("script");
|
|
|
|
return new Promise((res, rej) => {
|
|
Object.assign(script, {
|
|
type: "text/javascript",
|
|
async: true,
|
|
src: file.path || file,
|
|
onload: () => {
|
|
progress && progress(1);
|
|
res(script);
|
|
},
|
|
onerror: rej,
|
|
onreadystatechange: s => {
|
|
if (script.readyState == "complete") { }
|
|
},
|
|
});
|
|
|
|
head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
function createReporter(callback, childs, weight) {
|
|
const reporter = {
|
|
callback: callback,
|
|
childs: childs ? childs.slice() : undefined,
|
|
value: 0,
|
|
weight: weight || 1,
|
|
|
|
get report() {
|
|
return function (v) {
|
|
if (!this.childs) {
|
|
this.value = v * this.weight;
|
|
} else {
|
|
let summ = 0;
|
|
let v = 0;
|
|
|
|
this.childs.forEach((e) => {
|
|
summ += e.weight || 1;
|
|
v += (e.value || 0);
|
|
});
|
|
|
|
this.value = v / summ;
|
|
}
|
|
|
|
this.callback && this.callback(this.value);
|
|
}.bind(this);
|
|
},
|
|
};
|
|
|
|
if (childs) {
|
|
childs.forEach(e => {
|
|
e.callback = reporter.report;
|
|
});
|
|
}
|
|
|
|
return reporter;
|
|
}
|
|
|
|
function runLoadingProcess(jsFiles, binaryFiles, progressEvent = f => f, _debugScripts) {
|
|
const jsCount = jsFiles.length;
|
|
const binCount = binaryFiles.length;
|
|
|
|
const all = jsFiles.concat(binaryFiles);
|
|
|
|
const reporters = Array.from({ length: jsCount + binCount }, () => createReporter());
|
|
createReporter(progressEvent, reporters);
|
|
|
|
let pendings;
|
|
|
|
if (!_debugScripts) {
|
|
pendings = all.map((e, i) => loadBinary(e, reporters[i].report));
|
|
} else {
|
|
pendings = binaryFiles.map((e, i) => loadBinary(e, reporters[i].report))
|
|
pendings = pendings.concat(jsFiles.map((e, i) => loadScript(e, reporters[i + binCount].report)))
|
|
}
|
|
|
|
return Promise.all(pendings).then(data => {
|
|
return data.filter(e => e && e.type === 'swf');
|
|
});
|
|
}
|
|
|
|
let fillLine = undefined;
|
|
let __config = undefined;
|
|
let __splash = undefined;
|
|
let __pr__root = undefined;
|
|
let handleResize = undefined;
|
|
|
|
window["setStageDimensions"]=function(x, y, w, h){
|
|
__config.x=x;
|
|
__config.y=y;
|
|
__config.w=w;
|
|
__config.h=h;
|
|
if(window["AVMPlayerPoki"]){
|
|
window["AVMPlayerPoki"].setStageDimensions(x, y, w, h);
|
|
}
|
|
if(handleResize){
|
|
handleResize();
|
|
}
|
|
}
|
|
|
|
function runGame(progressEvent = f => f, completeEvent = f => f) {
|
|
|
|
if (!__config) {
|
|
init();
|
|
}
|
|
|
|
let jss = Array.isArray(__config.runtime) ? jss : [__config.runtime];
|
|
jss = jss.map(e => ({ path: e.path || e, size: e.size || 0 }));
|
|
|
|
const bins = __config.binary;
|
|
|
|
const loadReporter = createReporter(null, null, 4);
|
|
const avmReporter = createReporter((f) => {
|
|
console.log('AVM Load', f);
|
|
}, null, __config.progressParserWeigth ? __config.progressParserWeigth : 0.001);
|
|
|
|
createReporter(function (fill) {
|
|
fillLine(fill);
|
|
// rereport
|
|
progressEvent(fill);
|
|
}, [loadReporter, avmReporter])
|
|
|
|
const complete = f => {
|
|
// rereport
|
|
completeEvent(f);
|
|
|
|
if (__config.start) {
|
|
|
|
// start image exists.
|
|
// hide progressbar, show startimage and wait for user-input to start the game
|
|
|
|
Object.assign(__pr__root.style, {
|
|
visibility: "hidden",
|
|
opacity: 0,
|
|
});
|
|
Object.assign(__splash.style, {
|
|
backgroundImage: `url(${__config.start})`,
|
|
});
|
|
let onCLick = (e) => {
|
|
window.removeEventListener("click", onCLick);
|
|
Object.assign(__splash.style, {
|
|
visibility: "hidden",
|
|
opacity: 0,
|
|
});
|
|
if (!f)
|
|
throw ("PokiPlayer did not send a callback for starting game");
|
|
f();
|
|
window.setTimeout(()=>{
|
|
window.removeEventListener("resize", handleResize);
|
|
handleResize=null;
|
|
}, 500)
|
|
};
|
|
window.addEventListener("click", onCLick);
|
|
}
|
|
else {
|
|
// no start image.
|
|
// game will be started automatically
|
|
Object.assign(__splash.style, {
|
|
visibility: "hidden",
|
|
opacity: 0,
|
|
});
|
|
// use Timeout, so css transition can complete first
|
|
window.setTimeout(()=>{
|
|
window.removeEventListener("resize", handleResize);
|
|
handleResize=null;
|
|
}, 500)
|
|
}
|
|
};
|
|
|
|
runLoadingProcess(jss, bins, loadReporter.report, __config.debug).then(data => {
|
|
const runner = window["startPokiGame"];
|
|
if (!runner) {
|
|
throw "Could not find a 'startPokiGame' method";
|
|
}
|
|
|
|
__config.files = data;
|
|
|
|
runner(__config);
|
|
|
|
// now poki player is available at window["AVMPlayerPoki"]
|
|
// can be used to update the stageDimensions:
|
|
// window["AVMPlayerPoki"].setStageDimensions(x, y, w, h);
|
|
// values can be
|
|
// numbers (absolute pixel values)
|
|
// strings (percentage of window.innerHeight/innerWidth in 0-100)
|
|
});
|
|
|
|
// make functions avilailable on window, so the loaded js-code can access and execute them
|
|
Object.assign(window, {
|
|
updatePokiProgressBar: avmReporter.report,
|
|
pokiGameParseComplete: complete,
|
|
});
|
|
}
|
|
|
|
function init(config) {
|
|
if (!config) {
|
|
throw new Error("Config is required");
|
|
}
|
|
|
|
__config = config;
|
|
|
|
const splash = document.querySelector("#splash__image");
|
|
const pr__root = document.querySelector("#progress__root");
|
|
const pr__line = document.querySelector("#progress__line");
|
|
|
|
__splash = splash;
|
|
__pr__root = pr__root;
|
|
|
|
const pr_conf = config.progress;
|
|
pr_conf.rect = pr_conf.rect || [0, 0.9, 1, 0.2];
|
|
|
|
Object.assign(splash.style, {
|
|
backgroundImage: `url(${config.splash})`,
|
|
visibility: "visible",
|
|
});
|
|
|
|
Object.assign(pr__root.style, {
|
|
background: pr_conf.back,
|
|
left: `${100 * pr_conf.rect[0]}%`,
|
|
top: `${100 * pr_conf.rect[1]}%`,
|
|
width: `${100 * pr_conf.rect[2]}%`,
|
|
height: `${100 * pr_conf.rect[3]}%`,
|
|
});
|
|
|
|
Object.assign(pr__line.style, {
|
|
background: pr_conf.line,
|
|
});
|
|
|
|
fillLine = fill => {
|
|
switch (pr_conf.direction) {
|
|
case "tb": {
|
|
Object.assign(pr__line.style, {
|
|
height: `${fill * 100}%`,
|
|
width: "100%",
|
|
});
|
|
break;
|
|
}
|
|
case "lr":
|
|
default: {
|
|
Object.assign(pr__line.style, {
|
|
height: "100%",
|
|
width: `${fill * 100}%`,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
handleResize = () => {
|
|
let x=(typeof config.x==="string")?parseFloat(config.x.replace("%", ""))/100*window.innerWidth:config.x;
|
|
let y=(typeof config.y==="string")?parseFloat(config.y.replace("%", ""))/100*window.innerHeight:config.y;
|
|
let w=(typeof config.w==="string")?parseFloat(config.w.replace("%", ""))/100*window.innerWidth:config.w;
|
|
let h=(typeof config.h==="string")?parseFloat(config.h.replace("%", ""))/100*window.innerHeight:config.h;
|
|
|
|
if(!x) x=0;
|
|
if(!y) y=0;
|
|
if(!w) w=window.innerWidth;
|
|
if(!h) h=window.innerHeight;
|
|
|
|
const minMax = Math.min(h / config.height, w / config.width);
|
|
const rw = Math.ceil(config.width * minMax);
|
|
const rh = Math.ceil(config.height * minMax);
|
|
const rx = x+(w - rw) / 2;
|
|
const ry = y+(h - rh) / 2;
|
|
|
|
Object.assign(splash.style, {
|
|
width: `${rw}px`,
|
|
height: `${rh}px`,
|
|
left: `${rx}px`,
|
|
top: `${ry}px`,
|
|
});
|
|
};
|
|
|
|
window.addEventListener("resize", handleResize);
|
|
handleResize();
|
|
}
|
|
|
|
return {
|
|
init,
|
|
runGame,
|
|
};
|
|
})(); |