├── .gitignore ├── .gitmodules ├── .npmignore ├── .nvmrc ├── Makefile.common ├── Makefile.gambatte ├── Makefile.vbanext ├── README.md ├── dist ├── archjs-gambatte.js └── archjs-vbanext.js ├── example ├── index.html ├── main.js └── vendors │ ├── CoreJs-0.9.6.min.js │ └── FpsMeter-0.3.1.min.js ├── includes └── libretro.h ├── package.json └── sources ├── bridge_retro.c ├── bridge_retro.h ├── bridge_virtjs.c ├── bridge_virtjs.h ├── bridge_virtjs.js ├── epilogue.js ├── frontend.c ├── frontend.h ├── input_formats.c ├── input_formats.h ├── main.c └── prologue.js /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/archjs-gambatte 2 | /dist/archjs-vbanext 3 | 4 | .#* 5 | *.o 6 | *.tmp 7 | 8 | *.a 9 | *.bc 10 | 11 | *.gb 12 | *.gbc 13 | *.gba 14 | *.sav 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example/virtjs"] 2 | path = example/virtjs 3 | url = https://github.com/arcanis/virtjs.git 4 | [submodule "example/taisel"] 5 | path = example/taisel 6 | url = https://github.com/start9/taisel.git 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /example/ 2 | /includes/ 3 | /sources/ 4 | /systems/ 5 | 6 | /Makefile.* 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | node 2 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | ifndef ENGINE_NAME 2 | $(error "You need to define the engine architecture") 3 | endif 4 | 5 | ifndef ENGINE_ROM_NAME 6 | $(error "You need to define the engine default rom name") 7 | endif 8 | 9 | SHELL = bash 10 | 11 | CFLAGS = -std=c99 12 | 13 | CPPFLAGS += -W -Wall -Werror -Wno-unused-parameter 14 | CPPFLAGS += -I $(shell pwd)/includes 15 | 16 | ifdef EMMAKEN_COMPILER 17 | 18 | MEMORY = $$((64*1024*1024)) 19 | 20 | TARGET = dist/archjs-$(ENGINE_NAME).js 21 | 22 | SOURCES = \ 23 | systems/libretro-$(ENGINE_NAME).bc \ 24 | sources/bridge_retro.c.o \ 25 | sources/bridge_virtjs.js \ 26 | sources/frontend.c.o \ 27 | sources/input_formats.c.o \ 28 | sources/main.c.o 29 | 30 | EXTRA_SOURCES = \ 31 | sources/prologue.js \ 32 | sources/epilogue.js \ 33 | Makefile.$(ENGINE_NAME) \ 34 | Makefile.common 35 | 36 | LDFLAGS += --memory-init-file 0 37 | LDFLAGS += -s NO_EXIT_RUNTIME=1 -s INVOKE_RUN=0 -s TOTAL_MEMORY=$(MEMORY) 38 | 39 | else 40 | 41 | TARGET = dist/archjs-$(ENGINE_NAME) 42 | 43 | SOURCES = \ 44 | sources/bridge_retro.c.o \ 45 | sources/bridge_virtjs.c.o \ 46 | sources/frontend.c.o \ 47 | sources/input_formats.c.o \ 48 | sources/main.c.o 49 | 50 | EXTRA_SOURCES = \ 51 | systems/libretro-$(ENGINE_NAME).so \ 52 | Makefile.$(ENGINE_NAME) \ 53 | Makefile.common 54 | 55 | CPPFLAGS += $(shell pkg-config --cflags sdl2) 56 | 57 | LDFLAGS += -L systems -lretro-$(ENGINE_NAME) 58 | LDFLAGS += $(shell pkg-config --libs sdl2) 59 | 60 | endif 61 | 62 | ifdef DEBUG 63 | 64 | CPPFLAGS += -g 65 | 66 | else 67 | 68 | CPPFLAGS += -O3 69 | LDFLAGS += -O3 70 | 71 | ifdef EMMAKEN_COMPILER 72 | LDFLAGS += --llvm-lto 3 -s OUTLINING_LIMIT=50000 -s PRECISE_F32=2 73 | endif 74 | 75 | endif 76 | 77 | all: $(TARGET) 78 | 79 | $(TARGET): $(SOURCES) $(EXTRA_SOURCES) 80 | $(CXX) -o $(TARGET) $(filter-out %.js, $(SOURCES)) $(addprefix --js-library , $(filter %.js, $(SOURCES))) $(LDFLAGS) 81 | ifdef EMMAKEN_COMPILER 82 | cat sources/prologue.js | sed -e 's/@(ENGINE_NAME)/$(ENGINE_NAME)/g' -e 's/@(ENGINE_ROM_NAME)/$(ENGINE_ROM_NAME)/' > $(TARGET).tmp 83 | cat $(TARGET) >> $(TARGET).tmp 84 | cat sources/epilogue.js | sed -e 's/@(ENGINE_NAME)/$(ENGINE_NAME)/g' -e 's/@(ENGINE_ROM_NAME)/$(ENGINE_ROM_NAME)/' >> $(TARGET).tmp 85 | mv $(TARGET).tmp $(TARGET) 86 | endif 87 | 88 | %.c.o: %.c Makefile.$(ENGINE_NAME) Makefile.common 89 | $(CC) -c -o $(@) $(<) $(CFLAGS) $(CPPFLAGS) 90 | 91 | clean: 92 | @$(RM) -f **/*.o 93 | @$(RM) -f **/*.tmp 94 | 95 | fclean: clean 96 | @$(RM) -f $(TARGET) 97 | 98 | re: clean all 99 | 100 | .PHONY: all clean re 101 | -------------------------------------------------------------------------------- /Makefile.gambatte: -------------------------------------------------------------------------------- 1 | ENGINE_NAME=gambatte 2 | ENGINE_ROM_NAME=game.gb 3 | 4 | include Makefile.common 5 | -------------------------------------------------------------------------------- /Makefile.vbanext: -------------------------------------------------------------------------------- 1 | ENGINE_NAME=vbanext 2 | ENGINE_ROM_NAME=game.gba 3 | 4 | include Makefile.common 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archjs 2 | 3 | Archjs is a [Libretro](http://www.libretro.com/) frontend dedicated to one specific purpose : being easily compiled to Javascript using [Emscripten](http://kripken.github.io/emscripten-site/). 4 | 5 | Once compiled to Javascript, the scripts export [Virtjs](http://virtjs.com/) engines (available in the `Archjs` global), which can then be instanciated just like any other engine ([documentation here](http://virtjs.com/documentation/instanciating-an-emulator/)). 6 | 7 | ## Supported cores 8 | 9 | Since we're only a libretro frontend, we should work with most emscripten-compatible libretro cores. However, it is not so common, so you should check each one to see if they support it. The following cores have been tested with Archjs: 10 | 11 | - [Gambatte](https://github.com/libretro/gambatte-libretro) 12 | - [VBA-Next](https://github.com/libretro/vba-next) 13 | 14 | ## Multi-format cores 15 | 16 | Some cores may support multiple kinds of games. In such event, you may use the file name option of the `loadArrayBuffer()` method to tell the emulator about the actual file name (since they often rely on the file extension to select the right emulator). However, each core also has a main extension (such as `.gb` for Gambatte, or `.gba` for VBA-Next) which will be used if you omit to specify the arraybuffer file name. 17 | 18 | ## SDL frontend 19 | 20 | In order to be easily debugged, Archjs ships with a small SDL frontend, mimicking Virtjs devices. 21 | 22 | **Note** This frontend is only used when `make` is called without using emscripten, and is not present in the dist Javascript builds, which does not use the SDL at all (we instead except you to plug whatever device you want to usexs). 23 | 24 | ## License 25 | 26 | Archjs is supported and maintained by the [Start9](https://github.com/start9/) organization. The frontend is available under the [GPL v3 license](https://www.gnu.org/copyleft/gpl.html). 27 | 28 | ## Contributing 29 | 30 | If you notice a bug or want to suggest a feature, feel free to open an issue or pull request on the repository. However, keep in mind that the focus of Archjs is to remain simple (in order to be both easily compiled and heavily optimized by Emscripten). 31 | 32 | We also hang out on the #start9-dev irc network on Freenode, so feel free to join us there. 33 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Click to select a file
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | var AudiojsAudio = Audiojs.extra.AudiojsAudio; 2 | var KeyboardInput = Virtjs.devices.inputs.KeyboardInput; 3 | var WebGLScreen = Virtjs.devices.screens.WebGLScreen; 4 | var AnimationFrameTimer = Virtjs.devices.timers.AnimationFrameTimer; 5 | var fetchArrayBuffer = Virtjs.utils.DataUtils.fetchArrayBuffer; 6 | 7 | function listenShortcuts(engine) { 8 | 9 | var state = null; 10 | 11 | window.addEventListener('keydown', function (e) { 12 | 13 | if (e.keyCode !== 112 && e.keyCode !== 113) 14 | return ; 15 | 16 | e.preventDefault(); 17 | 18 | if (e.keyCode === 112) { 19 | state = engine.getState(); 20 | } else if (e.keyCode === 113) { 21 | state && engine.setState(state); 22 | } 23 | 24 | }); 25 | 26 | } 27 | 28 | function run(arrayBuffer, { fileName }) { 29 | 30 | var meter = new FPSMeter({ }); 31 | var Engine = Archjs.byName[ENGINE]; 32 | 33 | var canvas = document.querySelector('#screen'); 34 | 35 | var screen = new WebGLScreen({ canvas }); 36 | screen.setOutputSize(canvas.width, canvas.height); 37 | 38 | var input = new KeyboardInput({ codeMap: Engine.codeMap }); 39 | 40 | var timer = new AnimationFrameTimer(); 41 | 42 | timer.start(function () { 43 | meter.tickStart(); 44 | }, function () { 45 | meter.tick(); 46 | }); 47 | 48 | var audio = new AudiojsAudio(); 49 | 50 | var engine = new Engine({ devices: { 51 | screen, timer, input, audio 52 | } }); 53 | 54 | engine.loadArrayBuffer(arrayBuffer, { fileName }); 55 | 56 | listenShortcuts(engine); 57 | 58 | } 59 | 60 | function load(what, fileName) { 61 | 62 | return fetchArrayBuffer(what).then(function (arrayBuffer) { 63 | 64 | document.querySelector('#selector').style.display = 'none'; 65 | document.querySelector('#overlay').style.display = 'none'; 66 | 67 | return run(arrayBuffer, fileName); 68 | 69 | }); 70 | 71 | } 72 | 73 | if (GAMEPATH) { 74 | 75 | load(GAMEPATH, GAMEPATH.substr(GAMEPATH.lastIndexOf('/') + 1)); 76 | 77 | } else { 78 | 79 | var selector = document.querySelector('#selector'); 80 | 81 | selector.addEventListener('change', function () { 82 | load(selector.files[0], selector.files[0].name); 83 | }); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /example/vendors/CoreJs-0.9.6.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core.js 0.9.6 3 | * https://github.com/zloirock/core-js 4 | * License: http://rock.mit-license.org 5 | * © 2015 Denis Pushkarev 6 | */ 7 | !function(a){"use strict";var b=null,c=null;!function(c){function a(d){if(b[d])return b[d].exports;var e=b[d]={exports:{},id:d,loaded:!1};return c[d].call(e.exports,e,e.exports,a),e.loaded=!0,e.exports}var b={};return a.m=c,a.c=b,a.p="",a(0)}([function(b,c,a){a(1),a(4),a(2),a(5),a(3),a(6),a(10),a(9),a(8),a(7),a(11),a(12),a(13),a(14),a(15),a(16),a(18),a(17),a(19),a(20),a(21),a(22),a(23),a(24),a(25),a(26),a(27),a(38),a(31),a(28),a(29),a(30),a(32),a(33),a(34),a(35),a(36),a(37),a(39),a(40),a(41),a(42),a(43),a(44),a(45),a(46),a(47),a(48)},function(S,R,d){function D(a,b){return function(g){var c,e=r(g),f=0,d=[];for(c in e)c!=i&&l(e,c)&&d.push(c);for(;b>f;)l(e,c=a[f++])&&(~o.call(d,c)||d.push(c));return d}}function w(a){return!b.isObject(a)}function p(){}function B(a){return function(){return a.apply(b.ES5Object(this),arguments)}}function C(a){return function(h,d){g.fn(h);var c=r(this),e=s(c.length),b=a?e-1:0,f=a?-1:1;if(arguments.length<2)for(;;){if(b in c){d=c[b],b+=f;break}b+=f,g(a?b>=0:e>b,"Reduce of empty array with no initial value")}for(;a?b>=0:e>b;b+=f)b in c&&(d=h(d,c[b],b,this));return d}}function e(a){return a>9?a:"0"+a}var b=d(52),y=d(53),h=d(54),c=d(49),N=d(58),f=d(55),i=d(56).safe("__proto__"),g=d(57),u=g.obj,v=Object.prototype,m=[],n=m.slice,o=m.indexOf,A=h.classof,l=b.has,x=b.setDesc,M=b.getDesc,q=b.setDescs,z=b.isFunction,r=b.toObject,s=b.toLength,t=!1,P=d(59)(!1),H=f(0),I=f(1),J=f(2),K=f(3),L=f(4);if(!b.DESC){try{t=8==x(y("div"),"x",{get:function(){return 8}}).x}catch(Q){}b.setDesc=function(b,c,a){if(t)try{return x(b,c,a)}catch(d){}if("get"in a||"set"in a)throw TypeError("Accessors not supported!");return"value"in a&&(u(b)[c]=a.value),b},b.getDesc=function(c,d){if(t)try{return M(c,d)}catch(e){}return l(c,d)?b.desc(!v.propertyIsEnumerable.call(c,d),c[d]):a},b.setDescs=q=function(a,c){u(a);for(var d,e=b.getKeys(c),g=e.length,f=0;g>f;)b.setDesc(a,d=e[f++],c[d]);return a}}c(c.S+c.F*!b.DESC,"Object",{getOwnPropertyDescriptor:b.getDesc,defineProperty:b.setDesc,defineProperties:q});var j="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),F=j.concat("length","prototype"),G=j.length,k=function(){var a,c=y("iframe"),d=G,e=">";for(c.style.display="none",b.html.appendChild(c),c.src="javascript:",a=c.contentWindow.document,a.open(),a.write("