Establishing web architecture
This commit is contained in:
parent
4b3852a1d2
commit
0927a42107
|
@ -0,0 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
globalThis.App = class App {
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,214 @@
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
|
||||||
|
public class Bundle {
|
||||||
|
|
||||||
|
// File loaded from disk
|
||||||
|
static class File2 implements Comparable<File2> {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
byte[] data; // Contents
|
||||||
|
String filename; // Full path relative to root
|
||||||
|
|
||||||
|
// Comparator
|
||||||
|
public int compareTo(File2 o) {
|
||||||
|
if (filename.equals("app/_boot.js"))
|
||||||
|
return -1;
|
||||||
|
if (o.filename.equals("app/_boot.js"))
|
||||||
|
return 1;
|
||||||
|
return filename.compareTo(o.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all files in directory tree into memory
|
||||||
|
static HashMap<String, File2> readFiles(String bundleTitle) {
|
||||||
|
var dirs = new ArrayDeque<File>();
|
||||||
|
var root = new File(".");
|
||||||
|
var subs = new ArrayDeque<String>();
|
||||||
|
var files = new HashMap<String, File2>();
|
||||||
|
|
||||||
|
// Process all subdirectories
|
||||||
|
dirs.add(root);
|
||||||
|
while (!dirs.isEmpty()) {
|
||||||
|
var dir = dirs.remove();
|
||||||
|
|
||||||
|
// Add all subdirectories
|
||||||
|
for (var sub : dir.listFiles(f->f.isDirectory())) {
|
||||||
|
|
||||||
|
// Exclusions
|
||||||
|
if (dir == root && sub.getName().equals(".git"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add the directory for bundling
|
||||||
|
dirs.add(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all files
|
||||||
|
for (var file : dir.listFiles(f->f.isFile())) {
|
||||||
|
var file2 = new File2();
|
||||||
|
|
||||||
|
// Read the file into memory
|
||||||
|
try {
|
||||||
|
var stream = new FileInputStream(file);
|
||||||
|
file2.data = stream.readAllBytes();
|
||||||
|
stream.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the file's full pathname
|
||||||
|
subs.clear();
|
||||||
|
subs.addFirst(file.getName());
|
||||||
|
for (;;) {
|
||||||
|
file = file.getParentFile();
|
||||||
|
if (file.equals(root))
|
||||||
|
break;
|
||||||
|
subs.addFirst(file.getName());
|
||||||
|
}
|
||||||
|
file2.filename = String.join("/", subs);
|
||||||
|
|
||||||
|
// Exclusions
|
||||||
|
if (
|
||||||
|
file2.filename.startsWith(".git" ) ||
|
||||||
|
file2.filename.startsWith(bundleTitle + "_") &&
|
||||||
|
file2.filename.endsWith (".html" )
|
||||||
|
) continue;
|
||||||
|
|
||||||
|
// Add the file to the output
|
||||||
|
files.put(file2.filename, file2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend manifest object to _boot.js
|
||||||
|
static void manifest(HashMap<String, File2> files, String bundleName) {
|
||||||
|
|
||||||
|
// Produce a sorted list of files
|
||||||
|
var values = files.values().toArray(new File2[files.size()]);
|
||||||
|
Arrays.sort(values);
|
||||||
|
|
||||||
|
// Build a file manifest
|
||||||
|
var manifest = new StringBuilder();
|
||||||
|
manifest.append("\"use strict\";\nlet manifest=[");
|
||||||
|
for (var file : values) {
|
||||||
|
manifest.append("{" +
|
||||||
|
"name:\"" + file.filename + "\"," +
|
||||||
|
"size:" + file.data.length +
|
||||||
|
"},");
|
||||||
|
}
|
||||||
|
manifest.append("];\nlet bundleName=\"" + bundleName + "\";\n");
|
||||||
|
|
||||||
|
// Prepend the manifest to _boot.js
|
||||||
|
var boot = files.get("app/_boot.js");
|
||||||
|
boot.data = (
|
||||||
|
manifest.toString() +
|
||||||
|
new String(boot.data, StandardCharsets.UTF_8) +
|
||||||
|
"\u0000"
|
||||||
|
).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct bundled blob
|
||||||
|
static byte[] blob(HashMap<String, File2> files) {
|
||||||
|
|
||||||
|
// Produce a sorted list of files
|
||||||
|
var values = files.values().toArray(new File2[files.size()]);
|
||||||
|
Arrays.sort(values);
|
||||||
|
|
||||||
|
// Build the blob
|
||||||
|
var blob = new ByteArrayOutputStream();
|
||||||
|
for (var file : values) try {
|
||||||
|
blob.write(file.data);
|
||||||
|
} catch (Exception e) { }
|
||||||
|
|
||||||
|
return blob.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode bundled blob as a .png
|
||||||
|
static byte[] png(byte[] blob) {
|
||||||
|
|
||||||
|
// Calculate the dimensions of the image
|
||||||
|
int width = (int) Math.ceil(Math.sqrt(blob.length));
|
||||||
|
int height = (int) Math.ceil((double) blob.length / width);
|
||||||
|
|
||||||
|
// Prepare the pixel data
|
||||||
|
var pixels = new int[width * height];
|
||||||
|
for (int x = 0; x < blob.length; x++) {
|
||||||
|
int l = blob[x] & 0xFF;
|
||||||
|
pixels[x] = 0xFF000000 | l << 16 | l << 8 | l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a BufferedImage containing the pixels
|
||||||
|
var img = new BufferedImage(width, height,
|
||||||
|
BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
img.getRaster().setPixels(0, 0, width, height, pixels);
|
||||||
|
|
||||||
|
// Encode the image as a PNG byte array
|
||||||
|
var png = new ByteArrayOutputStream();
|
||||||
|
try { ImageIO.write(img, "png", png); }
|
||||||
|
catch (Exception e) { }
|
||||||
|
return png.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed bundle .png into template.html as a data URL
|
||||||
|
static void template(byte[] png, String bundleName) {
|
||||||
|
|
||||||
|
// Encode the PNG as a data URL
|
||||||
|
String url = "data:image/png;base64," +
|
||||||
|
Base64.getMimeEncoder().encodeToString(png)
|
||||||
|
.replaceAll("\\r\\n", "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Read template.html into memory
|
||||||
|
var inStream = new FileInputStream("app/template.html");
|
||||||
|
String template =
|
||||||
|
new String(inStream.readAllBytes(), StandardCharsets.UTF_8)
|
||||||
|
.replace("src=\"\"", "src=\"" + url + "\"")
|
||||||
|
;
|
||||||
|
inStream.close();
|
||||||
|
|
||||||
|
// Write the output HTML file
|
||||||
|
var outStream = new FileOutputStream(bundleName + ".html");
|
||||||
|
outStream.write(template.getBytes(StandardCharsets.UTF_8));
|
||||||
|
outStream.close();
|
||||||
|
} catch (Exception e) { throw new RuntimeException(e.getMessage()); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the filename of the bundle
|
||||||
|
static String bundleName(String name) {
|
||||||
|
var calendar = Calendar.getInstance();
|
||||||
|
return String.format("%s_%04d%02d%02d",
|
||||||
|
name,
|
||||||
|
calendar.get(Calendar.YEAR),
|
||||||
|
calendar.get(Calendar.MONTH) + 1,
|
||||||
|
calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program entry point
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.err.println("Usage: Bundle <name>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program tasks
|
||||||
|
String bundleName = bundleName(args[0]);
|
||||||
|
var files = readFiles(args[0]);
|
||||||
|
manifest(files, bundleName);
|
||||||
|
var blob = blob(files);
|
||||||
|
var png = png(blob);
|
||||||
|
template(png, bundleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
// Produce an async function from a source string
|
||||||
|
if (!globalThis.AsyncFunction)
|
||||||
|
globalThis.AsyncFunction =
|
||||||
|
Object.getPrototypeOf(async function(){}).constructor;
|
||||||
|
|
||||||
|
// Read scripts from files on disk to aid with debugging
|
||||||
|
let debug = location.hash == "#debug";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Bundle //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Resource asset manager
|
||||||
|
globalThis.Bundle = class BundledFile {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Static Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Adds a bundled file from loaded file data
|
||||||
|
static add(name, data) {
|
||||||
|
return Bundle.files[name] = new Bundle(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the file given its filename
|
||||||
|
static get(name) {
|
||||||
|
return Bundle.files[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a file as a JavaScript source file
|
||||||
|
static async run(name) {
|
||||||
|
await Bundle.files[name].run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a URL for a script source file
|
||||||
|
static script(name) {
|
||||||
|
return debug ? name : Bundle.files[name].toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(name, data) {
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.data = data;
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
// Detect the MIME type
|
||||||
|
this.mime =
|
||||||
|
name.endsWith(".css" ) ? "text/css;charset=UTF-8" :
|
||||||
|
name.endsWith(".frag") ? "text/plain;charset=UTF-8" :
|
||||||
|
name.endsWith(".js" ) ? "text/javascript;charset=UTF-8" :
|
||||||
|
name.endsWith(".png" ) ? "image/png" :
|
||||||
|
name.endsWith(".svg" ) ? "image/svg+xml;charset=UTF-8" :
|
||||||
|
name.endsWith(".txt" ) ? "text/plain;charset=UTF-8" :
|
||||||
|
name.endsWith(".vert") ? "text/plain;charset=UTF-8" :
|
||||||
|
name.endsWith(".wasm") ? "application/wasm" :
|
||||||
|
"application/octet-stream"
|
||||||
|
;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Execute the file as a JavaScript source file
|
||||||
|
async run() {
|
||||||
|
|
||||||
|
// Not running in debug mode
|
||||||
|
if (!debug) {
|
||||||
|
await new AsyncFunction(this.toString())();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running in debug mode
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
let script = document.createElement("script");
|
||||||
|
document.head.appendChild(script);
|
||||||
|
script.addEventListener("load", ()=>resolve());
|
||||||
|
script.src = this.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a blob from the file data
|
||||||
|
toBlob() {
|
||||||
|
return new Blob(this.data, { type: this.mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a blob URL for the file data
|
||||||
|
toBlobURL() {
|
||||||
|
return URL.createObjectURL(this.toBlob());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the file data as a data URL
|
||||||
|
toDataURL() {
|
||||||
|
return "data:" + this.mime + ";base64," + btoa(this.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the file data as a UTF-8 string
|
||||||
|
toString() {
|
||||||
|
return new TextDecoder().decode(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
Bundle.files = [];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ZIP Bundler //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Data buffer utility processor
|
||||||
|
class Bin {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor() {
|
||||||
|
this.data = [];
|
||||||
|
this.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Convert the data contents to a byte array
|
||||||
|
toByteArray() {
|
||||||
|
return Uint8Array.from(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode a byte array
|
||||||
|
writeBytes(data) {
|
||||||
|
this.data = this.data.concat(Array.from(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode a sized integer
|
||||||
|
writeInt(length, value) {
|
||||||
|
for (value &= 0xFFFFFFFF; length > 0; length--, value >>>= 8)
|
||||||
|
this.data.push(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode a string as UTF-8 with prepended length
|
||||||
|
writeString(value) {
|
||||||
|
this.writeBytes(new TextEncoder().encode(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the CRC32 lookup table
|
||||||
|
let crcLookup = new Uint32Array(256);
|
||||||
|
for (let x = 0; x <= 255; x++) {
|
||||||
|
let l = x;
|
||||||
|
for (let j = 7; j >= 0; j--)
|
||||||
|
l = ((l >>> 1) ^ (0xEDB88320 & -(l & 1)));
|
||||||
|
crcLookup[x] = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the CRC32 checksum for a byte array
|
||||||
|
function crc32(data) {
|
||||||
|
let c = 0xFFFFFFFF;
|
||||||
|
for (let x = 0; x < data.length; x++)
|
||||||
|
c = ((c >>> 8) ^ crcLookup[(c ^ data[x]) & 0xFF]);
|
||||||
|
return ~c & 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a .ZIP header from a bundled file
|
||||||
|
function toZipHeader(file, crc32, offset) {
|
||||||
|
let central = offset || offset === 0;
|
||||||
|
let ret = new Bin();
|
||||||
|
if (central) {
|
||||||
|
ret.writeInt (4, 0x02014B50); // Signature
|
||||||
|
ret.writeInt (2, 20); // Version created by
|
||||||
|
} else
|
||||||
|
ret.writeInt (4, 0x04034B50); // Signature
|
||||||
|
ret.writeInt (2, 20); // Version required
|
||||||
|
ret.writeInt (2, 0); // Bit flags
|
||||||
|
ret.writeInt (2, 0); // Compression method
|
||||||
|
ret.writeInt (2, 0); // Modified time
|
||||||
|
ret.writeInt (2, 0); // Modified date
|
||||||
|
ret.writeInt (4, crc32); // Checksum
|
||||||
|
ret.writeInt (4, file.data.length); // Compressed size
|
||||||
|
ret.writeInt (4, file.data.length); // Uncompressed size
|
||||||
|
ret.writeInt (2, file.name.length); // Filename length
|
||||||
|
ret.writeInt (2, 0); // Extra field length
|
||||||
|
if (central) {
|
||||||
|
ret.writeInt (2, 0); // File comment length
|
||||||
|
ret.writeInt (2, 0); // Disk number start
|
||||||
|
ret.writeInt (2, 0); // Internal attributes
|
||||||
|
ret.writeInt (4, 0); // External attributes
|
||||||
|
ret.writeInt (4, offset); // Relative offset
|
||||||
|
}
|
||||||
|
ret.writeString (file.name, true); // Filename
|
||||||
|
if (!central)
|
||||||
|
ret.writeBytes(file.data); // File data
|
||||||
|
return ret.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package all bundled files into a .zip file for download
|
||||||
|
Bundle.save = function() {
|
||||||
|
let centrals = new Array(manifest.length);
|
||||||
|
let locals = new Array(manifest.length);
|
||||||
|
let offset = 0;
|
||||||
|
let size = 0;
|
||||||
|
|
||||||
|
// Encode file and directory entries
|
||||||
|
let keys = Object.keys(Bundle.files);
|
||||||
|
for (let x = 0; x < keys.length; x++) {
|
||||||
|
let file = Bundle.get(keys[x]);
|
||||||
|
let sum = crc32(file.data);
|
||||||
|
locals [x] = toZipHeader(file, sum);
|
||||||
|
centrals[x] = toZipHeader(file, sum, offset);
|
||||||
|
offset += locals [x].length;
|
||||||
|
size += centrals[x].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode end of central directory
|
||||||
|
let end = new Bin();
|
||||||
|
end.writeInt(4, 0x06054B50); // Signature
|
||||||
|
end.writeInt(2, 0); // Disk number
|
||||||
|
end.writeInt(2, 0); // Central dir start disk
|
||||||
|
end.writeInt(2, centrals.length); // # central dir this disk
|
||||||
|
end.writeInt(2, centrals.length); // # central dir total
|
||||||
|
end.writeInt(4, size); // Size of central dir
|
||||||
|
end.writeInt(4, offset); // Offset of central dir
|
||||||
|
end.writeInt(2, 0); // .ZIP comment length
|
||||||
|
|
||||||
|
// Prompt the user to save the resulting file
|
||||||
|
let a = document.createElement("a");
|
||||||
|
a.download = bundleName + ".zip";
|
||||||
|
a.href = URL.createObjectURL(new Blob(
|
||||||
|
locals.concat(centrals).concat([end.toByteArray()]),
|
||||||
|
{ type: "application/zip" }
|
||||||
|
));
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Boot Loader //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
The Bundle.java utility prepends a manifest object to _boot.js that is an
|
||||||
|
array of file infos, each of which has a name and size property.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Remove the bundle image element from the document
|
||||||
|
arguments[0].remove();
|
||||||
|
|
||||||
|
// Process all files from the bundle blob
|
||||||
|
let blob = arguments[1];
|
||||||
|
let offset = arguments[2] - manifest[0].size * 4;
|
||||||
|
for (let file of manifest) {
|
||||||
|
let data = new Uint8Array(file.size);
|
||||||
|
for (let x = 0; x < file.size; x++, offset += 4)
|
||||||
|
data[x] = blob[offset];
|
||||||
|
if (file == manifest[0])
|
||||||
|
offset += 4;
|
||||||
|
Bundle.add(file.name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program startup
|
||||||
|
let run = async function() {
|
||||||
|
Bundle.run("app/App.js");
|
||||||
|
new App();
|
||||||
|
};
|
||||||
|
run();
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Virtual Boy Emulator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img alt="" style="display: none;" onload="((a,b,c,d)=>{d=document.createElement('canvas');d.width=b;d.height=c;d=d.getContext('2d');d.drawImage(a,0,0);b=d.getImageData(0,0,b,c).data;for(c=0,d='';b[c];c+=4)d+=String.fromCodePoint(b[c]);new Function(d)(a,b,c)})(this,width,height)" src="">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,37 @@
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo
|
||||||
|
@echo "Virtual Boy Emulator - August 22, 2021"
|
||||||
|
@echo
|
||||||
|
@echo "Target build environment is any Debian with the following packages:"
|
||||||
|
@echo " emscripten"
|
||||||
|
@echo " gcc"
|
||||||
|
@echo " openjdk-17-jdk"
|
||||||
|
@echo
|
||||||
|
@echo "Available make targets:"
|
||||||
|
@echo " bundle Package the repository for HTML distribution"
|
||||||
|
@echo " clean Remove output files"
|
||||||
|
@echo " core Check the C core source for compiler warnings"
|
||||||
|
@echo " wasm Build the WebAssembly core module"
|
||||||
|
@echo
|
||||||
|
|
||||||
|
.PHONY: bundle
|
||||||
|
bundle:
|
||||||
|
@java app/Bundle.java vbemu
|
||||||
|
# @gcc -c core/libvb.c -std=c90 -Wall -Wextra
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
@rm -f vbemu_*.html core.wasm
|
||||||
|
|
||||||
|
.PHONY: core
|
||||||
|
core:
|
||||||
|
@echo "Check C core for style warnings"
|
||||||
|
|
||||||
|
.PHONY: wasm
|
||||||
|
wasm:
|
||||||
|
@echo "Build WASM core module"
|
||||||
|
# @emcc -o core.wasm wasm/*.c core/libvb.c -Icore \
|
||||||
|
# --no-entry -O2 -flto -s WASM=1 -s EXPORTED_RUNTIME_METHODS=[] \
|
||||||
|
# -s ALLOW_MEMORY_GROWTH -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
||||||
|
@rm -f *.wasm.tmp*
|
Loading…
Reference in New Issue