添加新的fc模拟器页面
BIN
favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
686
fc2/css/fc.css
Normal file
@ -0,0 +1,686 @@
|
||||
@charset "utf-8";
|
||||
a,mark {
|
||||
color:inherit
|
||||
}
|
||||
html {
|
||||
color:#000;
|
||||
background:#fff;
|
||||
font-family:Arial,'Microsoft YaHei';
|
||||
font-size:34px;
|
||||
-webkit-user-select:none;
|
||||
user-select:none;
|
||||
-webkit-overflow-scrolling:touch
|
||||
}
|
||||
body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,strong,td,textarea,th,ul {
|
||||
padding:0;
|
||||
margin:0
|
||||
}
|
||||
table {
|
||||
border-collapse:collapse;
|
||||
border-spacing:0
|
||||
}
|
||||
img {
|
||||
display:block;
|
||||
border:0;
|
||||
max-width:100%
|
||||
}
|
||||
a {
|
||||
text-decoration:none;
|
||||
outline:0
|
||||
}
|
||||
em,i,strong {
|
||||
font-style:inherit;
|
||||
font-weight:inherit
|
||||
}
|
||||
li {
|
||||
list-style:none
|
||||
}
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
font-size:100%;
|
||||
font-weight:400
|
||||
}
|
||||
button,input,optgroup,option,select,textarea {
|
||||
font-family:inherit;
|
||||
font-size:inherit;
|
||||
font-style:inherit;
|
||||
font-weight:inherit
|
||||
}
|
||||
mark {
|
||||
background-color:rgba(0,0,0,0)
|
||||
}
|
||||
.clearfix:after {
|
||||
content:"\200B";
|
||||
display:block;
|
||||
height:0;
|
||||
clear:both
|
||||
}
|
||||
.none,[v-cloak] {
|
||||
display:none
|
||||
}
|
||||
.dire-wrap,.key-wrap {
|
||||
display:inline-block;
|
||||
overflow:hidden;
|
||||
will-change:auto;
|
||||
contain:style;
|
||||
backface-visibility:hidden
|
||||
}
|
||||
.mask {
|
||||
position:fixed;
|
||||
width:100%;
|
||||
height:100%;
|
||||
left:0;
|
||||
top:0;
|
||||
z-index:95;
|
||||
background:rgba(0,0,0,.7)
|
||||
}
|
||||
.tx_ov {
|
||||
overflow:hidden;
|
||||
white-space:nowrap;
|
||||
text-overflow:ellipsis
|
||||
}
|
||||
.tx_wrap {
|
||||
word-break:break-all;
|
||||
word-wrap:break-word
|
||||
}
|
||||
body {
|
||||
background:#000
|
||||
}
|
||||
.dc-centre,.dc-dire,.dire-ctrl,.key-btn {
|
||||
background-size:100% 100%
|
||||
}
|
||||
#screen {
|
||||
position:absolute;
|
||||
width:256px;
|
||||
height:240px;
|
||||
top:calc((100vh + 50px - 256px)/ 2);
|
||||
right: 240px;
|
||||
transform:scale(3.3333,3.3333) rotate(90deg);
|
||||
border:none;
|
||||
overflow:hidden
|
||||
}
|
||||
.ctrl-wrap {
|
||||
position:fixed;
|
||||
height:100vh;
|
||||
width:100%;
|
||||
top:0;
|
||||
left:0
|
||||
}
|
||||
.dc-centre,.dc-dire,.dire-ctrl,.dire-wrap,.key-btn,.key-wrap {
|
||||
position:absolute
|
||||
}
|
||||
.dire-wrap {
|
||||
width:390px;
|
||||
height:390px;
|
||||
top:-10px;
|
||||
left:-10px
|
||||
}
|
||||
.dire-ctrl {
|
||||
width:290px;
|
||||
height:290px;
|
||||
margin:50px;
|
||||
background-image:url(../images/7.png);
|
||||
overflow:hidden
|
||||
}
|
||||
.dc-centre {
|
||||
width:124px;
|
||||
height:124px;
|
||||
background-image:url(../images/8.png);
|
||||
will-change:auto;
|
||||
contain:style
|
||||
}
|
||||
.dc-dire {
|
||||
width:384px;
|
||||
height:384px;
|
||||
margin:3px;
|
||||
background-image:url(../images/9.png);
|
||||
will-change:auto;
|
||||
contain:style
|
||||
}
|
||||
.key-wrap {
|
||||
width:100vw;
|
||||
height:390px;
|
||||
bottom:20px;
|
||||
left:20px
|
||||
}
|
||||
.key-btn {
|
||||
width:150px;
|
||||
height:150px;
|
||||
line-height:150px;
|
||||
text-align:center;
|
||||
font-size:56px;
|
||||
transform:rotate(90deg);
|
||||
font-weight:700;
|
||||
color:rgba(255,255,255,.5);
|
||||
opacity:.5;
|
||||
background-image:url(../images/8.png)
|
||||
}
|
||||
.key-btn.kay-B {
|
||||
top:88px;
|
||||
left:90px;
|
||||
opacity:.7
|
||||
}
|
||||
.key-btn.kay-A {
|
||||
top:220px;
|
||||
left:0px;
|
||||
opacity:.7
|
||||
}
|
||||
.key-btn.kay-Y {
|
||||
top:88px;
|
||||
left:232px
|
||||
}
|
||||
.key-btn.kay-X {
|
||||
top:256px;
|
||||
left:162px
|
||||
}
|
||||
.key-btn.kay-AB {
|
||||
top:220px;
|
||||
left:222px
|
||||
}
|
||||
.key-btn.flat-btn {
|
||||
width:120px;
|
||||
height:80px;
|
||||
font-size:30px;
|
||||
line-height:80px
|
||||
}
|
||||
.key-btn.kay-SE {
|
||||
top:120px;
|
||||
right:60px
|
||||
}
|
||||
.key-btn.kay-ST {
|
||||
top:260px;
|
||||
right:60px
|
||||
}
|
||||
.btn-on {
|
||||
transform:scale(.9,.9) rotate(90deg)
|
||||
}
|
||||
.layui-m-layer {
|
||||
position:relative;
|
||||
z-index:19891014
|
||||
}
|
||||
.layui-m-layer * {
|
||||
-webkit-box-sizing:content-box;
|
||||
-moz-box-sizing:content-box;
|
||||
box-sizing:content-box
|
||||
}
|
||||
.layui-m-layermain,.layui-m-layershade {
|
||||
position:fixed;
|
||||
left:0;
|
||||
top:0;
|
||||
width:100%;
|
||||
height:100%
|
||||
}
|
||||
.layui-m-layershade {
|
||||
background-color:rgba(0,0,0,.7);
|
||||
pointer-events:auto
|
||||
}
|
||||
.layui-m-layermain {
|
||||
display:table;
|
||||
pointer-events:none
|
||||
}
|
||||
.layui-m-layermain .layui-m-layersection {
|
||||
display:table-cell;
|
||||
vertical-align:middle;
|
||||
text-align:center
|
||||
}
|
||||
.layui-m-layerchild {
|
||||
position:relative;
|
||||
display:inline-block;
|
||||
text-align:left;
|
||||
background-color:#fff;
|
||||
font-size:2.6vh;
|
||||
border-radius:20px;
|
||||
box-shadow:0 0 8px rgba(0,0,0,.1);
|
||||
pointer-events:auto;
|
||||
-webkit-overflow-scrolling:touch;
|
||||
-webkit-animation-fill-mode:both;
|
||||
animation-fill-mode:both;
|
||||
-webkit-animation-duration:.2s;
|
||||
animation-duration:.2s
|
||||
}
|
||||
@-webkit-keyframes layui-m-anim-scale {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:scale(.5);
|
||||
transform:scale(.5)
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
-webkit-transform:scale(1);
|
||||
transform:scale(1)
|
||||
}
|
||||
}@keyframes layui-m-anim-scale {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:scale(.5);
|
||||
transform:scale(.5)
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
-webkit-transform:scale(1);
|
||||
transform:scale(1)
|
||||
}
|
||||
}.layui-m-anim-scale {
|
||||
animation-name:layui-m-anim-scale;
|
||||
-webkit-animation-name:layui-m-anim-scale
|
||||
}
|
||||
@-webkit-keyframes layui-m-anim-up {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:translateY(1600px);
|
||||
transform:translateY(1600px)
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
-webkit-transform:translateY(0);
|
||||
transform:translateY(0)
|
||||
}
|
||||
}@keyframes layui-m-anim-up {
|
||||
0% {
|
||||
opacity:0;
|
||||
-webkit-transform:translateY(1600px);
|
||||
transform:translateY(1600px)
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
-webkit-transform:translateY(0);
|
||||
transform:translateY(0)
|
||||
}
|
||||
}.layui-m-anim-up {
|
||||
-webkit-animation-name:layui-m-anim-up;
|
||||
animation-name:layui-m-anim-up
|
||||
}
|
||||
.layui-m-layer0 .layui-m-layerchild {
|
||||
width:70%
|
||||
}
|
||||
.layui-m-layer1 .layui-m-layerchild {
|
||||
border:none;
|
||||
border-radius:0
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layerchild {
|
||||
width:15vh;
|
||||
border:none;
|
||||
padding:35px 0;
|
||||
box-shadow:none;
|
||||
color:#fff;
|
||||
background-color:rgba(0,0,0,.7)
|
||||
}
|
||||
.layui-m-layerchild h3 {
|
||||
padding:0 5%;
|
||||
height:7.5vh;
|
||||
line-height:7.5vh;
|
||||
font-size:2.8vh;
|
||||
font-weight:400;
|
||||
border-radius:20px 20px 0 0;
|
||||
text-align:center;
|
||||
border-bottom:1px solid #E0E0E0
|
||||
}
|
||||
.layui-m-layerbtn span,.layui-m-layerchild h3 {
|
||||
text-overflow:ellipsis;
|
||||
overflow:hidden;
|
||||
white-space:nowrap
|
||||
}
|
||||
.layui-m-layercont {
|
||||
padding:8% 10%;
|
||||
line-height:4vh;
|
||||
text-align:center
|
||||
}
|
||||
.layui-m-layer1 .layui-m-layercont {
|
||||
padding:0;
|
||||
text-align:left
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layercont {
|
||||
text-align:center;
|
||||
padding:0;
|
||||
line-height:0
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layercont i {
|
||||
width:3vh;
|
||||
height:3vh;
|
||||
margin-left:1vh;
|
||||
display:inline-block;
|
||||
background-color:#fff;
|
||||
border-radius:100%;
|
||||
-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;
|
||||
animation:layui-m-anim-loading 1.4s infinite ease-in-out;
|
||||
-webkit-animation-fill-mode:both;
|
||||
animation-fill-mode:both
|
||||
}
|
||||
.layui-m-layerbtn,.layui-m-layerbtn span {
|
||||
position:relative;
|
||||
text-align:center;
|
||||
border-radius:0 0 20px 20px
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layercont p {
|
||||
margin-top:2vh;
|
||||
font-size:2.5vh
|
||||
}
|
||||
@-webkit-keyframes layui-m-anim-loading {
|
||||
0%,100%,80% {
|
||||
transform:scale(0);
|
||||
-webkit-transform:scale(0)
|
||||
}
|
||||
40% {
|
||||
transform:scale(1);
|
||||
-webkit-transform:scale(1)
|
||||
}
|
||||
}@keyframes layui-m-anim-loading {
|
||||
0%,100%,80% {
|
||||
transform:scale(0);
|
||||
-webkit-transform:scale(0)
|
||||
}
|
||||
40% {
|
||||
transform:scale(1);
|
||||
-webkit-transform:scale(1)
|
||||
}
|
||||
}.layui-m-layer2 .layui-m-layercont i:first-child {
|
||||
margin-left:0;
|
||||
-webkit-animation-delay:-.32s;
|
||||
animation-delay:-.32s
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layercont i.layui-m-layerload {
|
||||
-webkit-animation-delay:-.16s;
|
||||
animation-delay:-.16s
|
||||
}
|
||||
.layui-m-layer2 .layui-m-layercont>div {
|
||||
line-height:3.3vh;
|
||||
padding-top:14px;
|
||||
margin-bottom:40px;
|
||||
font-size:3vh
|
||||
}
|
||||
.layui-m-layerbtn {
|
||||
display:block;
|
||||
display:-moz-box;
|
||||
display:-webkit-box;
|
||||
width:100%;
|
||||
height:7.5vh;
|
||||
line-height:7.5vh;
|
||||
font-size:0;
|
||||
border-top:1px solid #ddd;
|
||||
background-color:#FFF
|
||||
}
|
||||
.layui-m-layerbtn span {
|
||||
display:block;
|
||||
-moz-box-flex:1;
|
||||
box-flex:1;
|
||||
-webkit-box-flex:1;
|
||||
font-size:2.8vh;
|
||||
cursor:pointer;
|
||||
color:#0084ff
|
||||
}
|
||||
.layui-m-layerbtn span[yes] {
|
||||
border-radius:0 0 20px
|
||||
}
|
||||
.layui-m-layerbtn span[no] {
|
||||
border-right:1px solid #ddd;
|
||||
border-radius:0 0 0 20px
|
||||
}
|
||||
.layui-m-layerbtn span:active {
|
||||
background-color:#F6F6F6
|
||||
}
|
||||
.layui-m-layerend {
|
||||
position:absolute;
|
||||
right:14px;
|
||||
top:20px;
|
||||
width:60px;
|
||||
height:60px;
|
||||
border:0;
|
||||
font-weight:400;
|
||||
background:0 0;
|
||||
cursor:pointer;
|
||||
-webkit-appearance:none;
|
||||
font-size:6vh
|
||||
}
|
||||
.layui-m-layerend::after,.layui-m-layerend::before {
|
||||
position:absolute;
|
||||
left:10px;
|
||||
top:30px;
|
||||
content:'';
|
||||
width:18px;
|
||||
height:1px;
|
||||
background-color:#999;
|
||||
transform:rotate(45deg);
|
||||
-webkit-transform:rotate(45deg);
|
||||
border-radius:12px
|
||||
}
|
||||
.layui-m-layerend::after {
|
||||
transform:rotate(-45deg);
|
||||
-webkit-transform:rotate(-45deg)
|
||||
}
|
||||
body .layui-m-layer .layui-m-layer-footer {
|
||||
position:fixed;
|
||||
width:95%;
|
||||
max-width:100%;
|
||||
margin:0 auto;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:20px;
|
||||
background:0 0
|
||||
}
|
||||
.layui-m-layer-footer .layui-m-layercont {
|
||||
padding:20px;
|
||||
border-radius:20px 20px 0 0;
|
||||
background-color:rgba(255,255,255,.8)
|
||||
}
|
||||
.layui-m-layer-footer .layui-m-layerbtn {
|
||||
display:block;
|
||||
height:auto;
|
||||
background:0 0;
|
||||
border-top:none
|
||||
}
|
||||
.layui-m-layer-footer .layui-m-layerbtn span {
|
||||
background-color:rgba(255,255,255,.8)
|
||||
}
|
||||
.layui-m-layer-footer .layui-m-layerbtn span[no] {
|
||||
color:#FD482C;
|
||||
border-top:1px solid #c2c2c2;
|
||||
border-radius:0 0 20px 20px
|
||||
}
|
||||
.layui-m-layer-footer .layui-m-layerbtn span[yes] {
|
||||
margin-top:20px;
|
||||
border-radius:20px
|
||||
}
|
||||
body .layui-m-layer .layui-m-layer-msg {
|
||||
width:auto;
|
||||
max-width:90%;
|
||||
margin:0 auto;
|
||||
background-color:rgba(0,0,0,.7);
|
||||
color:#fff
|
||||
}
|
||||
.layui-m-layer-msg .layui-m-layercont {
|
||||
padding:20px 40px;
|
||||
font-size:2.8vh
|
||||
}
|
||||
/*html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: none;
|
||||
}
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}*/
|
||||
/*span {
|
||||
color: red;
|
||||
bottom: 0px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
display: none;
|
||||
font-size: 40pt;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
vertical-align: bottom;
|
||||
font-family: monospace;
|
||||
display:none;
|
||||
}*/
|
||||
.background-div{
|
||||
position: fixed;
|
||||
background-color: #000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.info-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 110vw;
|
||||
height: 250vw;
|
||||
background-color: #000;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-image:url(../images/invite-mask.jpg);
|
||||
background-repeat:no-repeat;
|
||||
background-size: 100%
|
||||
}
|
||||
#mask-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
.container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.real .dire-ctrl {
|
||||
background-image:url(../images/rocker.png);
|
||||
}
|
||||
|
||||
.real .dc-centre {
|
||||
display: none;
|
||||
}
|
||||
.real .dc-dire {
|
||||
background-image:url(../images/direct-shadow.png);
|
||||
}
|
||||
|
||||
.real .direct-wrap {
|
||||
display: grid;
|
||||
}
|
||||
.direct-btn {
|
||||
}
|
||||
.real .key-btn {
|
||||
opacity:1!important;
|
||||
background-image:url(../images/button.png)!important;
|
||||
}
|
||||
|
||||
.real .flat-btn {
|
||||
background-image:url(../images/flat-button.png)!important;;
|
||||
opacity:.7!important;;
|
||||
width:150px!important;
|
||||
}
|
||||
.real .kay-SE {
|
||||
top:100px!important;
|
||||
}
|
||||
.real .kay-ST {
|
||||
right:60px!important;;
|
||||
}
|
||||
.hidden {
|
||||
display: none!important;
|
||||
}
|
||||
.menu-back {
|
||||
background-color: #000;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right:0;
|
||||
display: none;
|
||||
}
|
||||
.menu-container {
|
||||
background-color: white;
|
||||
width: 100vw;
|
||||
height: 60vw;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -50vw;
|
||||
margin-top: -30vw;
|
||||
transform: rotate(90deg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.menu-container .menu-title {
|
||||
height: 80px;
|
||||
padding: 10px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #777;
|
||||
}
|
||||
.menu-container .menu-body {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
.menu-body .row {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
}
|
||||
.menu-body .row .row-title {
|
||||
width: 30%;
|
||||
color: #777;
|
||||
}
|
||||
.menu-body .row .row-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
.row-body .row-part{
|
||||
width: 50%;
|
||||
}
|
||||
.row-part input[type='radio']{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.menu-container .menu-footer {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
}
|
||||
.menu-container .menu-footer .left-part {
|
||||
flex:1;
|
||||
}
|
||||
.menu-container .menu-footer .right-part {
|
||||
display: flex;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
.menu-container .menu-footer .menu-btn {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
margin-right: 10px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.menu-btn.btn-success{
|
||||
color: #fff;
|
||||
background-color: #449d44;
|
||||
border-color: #398439;
|
||||
}
|
||||
#menu_btn{
|
||||
top: 120px;
|
||||
right: 60px;
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
font-size: 30px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
transform: rotate(90deg);
|
||||
font-weight: 700;
|
||||
color: rgba(255,255,255,.5);
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
background-size: 100% 100%;
|
||||
background-image: url(../images/8.png);
|
||||
z-index: 10;
|
||||
}
|
||||
#menu_btn.real{
|
||||
background-image: url(../images/flat-button.png);
|
||||
}
|
BIN
fc2/images/7.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
fc2/images/8.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
fc2/images/9.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
fc2/images/button-press.png
Executable file
After Width: | Height: | Size: 11 KiB |
BIN
fc2/images/button.png
Executable file
After Width: | Height: | Size: 12 KiB |
BIN
fc2/images/direct-shadow.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
fc2/images/flat-button-press.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
fc2/images/flat-button.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
fc2/images/invite-mask.jpg
Normal file
After Width: | Height: | Size: 386 KiB |
372
fc2/images/kingsomevs.proto
Normal file
@ -0,0 +1,372 @@
|
||||
//syntax = "proto2";
|
||||
|
||||
package kingsomevs;
|
||||
|
||||
/*
|
||||
约定:
|
||||
CM前缀:客户端发给服务器的消息(client message)
|
||||
SM前缀:服务器发给客户的的消息(server message)
|
||||
MF前缀:消息的内嵌字段,只能作为其他消息的内嵌字段不能send(message field)
|
||||
custom_data: 自定义用户数据,透传数据
|
||||
_e后缀:枚举类型
|
||||
|
||||
网络包格式:msghead + msgbody
|
||||
msghead: packagelen + msgid + magiccode = 2 + 2 + 4 = 8字节
|
||||
msgbody: protobuf数据
|
||||
msghead说明
|
||||
packagelen(unsigned short): 双字节网络包长度,
|
||||
msgid(unsigned short): 双字节消息id
|
||||
magiccode(unsigned int): 4字节魔数,并且为固定常数0xAABBCCAA,占位符客户端不需什么处理
|
||||
*/
|
||||
|
||||
//操作结果公用类型
|
||||
message MFOptResult
|
||||
{
|
||||
optional int32 error_code = 1; //错误代码
|
||||
optional string error_msg = 2; //错误描述
|
||||
optional string debug_msg = 3; //调试用
|
||||
}
|
||||
|
||||
//房间用户信息
|
||||
message MFRoomUserInfo
|
||||
{
|
||||
optional string account_id = 1; //用户id
|
||||
optional string user_profile = 2; //玩家简介,可以填写昵称、段位等信息
|
||||
optional int32 user_idx = 3; //用户索引,按用户加入房间的顺序递增
|
||||
}
|
||||
|
||||
//房间机器人信息
|
||||
message MFRoomRobotInfo
|
||||
{
|
||||
optional string robot_uniid = 1; //机器人唯一id
|
||||
optional string robot_name = 2; //机器人名字
|
||||
optional string avatar_url = 3; //头像地址,空的话本地随机头像
|
||||
}
|
||||
|
||||
//房间事件信息
|
||||
message MFRoomEventInfo
|
||||
{
|
||||
optional string custom_data = 1; //透传字段
|
||||
optional string src_account_id = 2; //事件发起者id
|
||||
}
|
||||
|
||||
//房间信息
|
||||
message MFRoomInfo
|
||||
{
|
||||
optional string room_id = 1; //房间
|
||||
optional string owner_id = 2; //房间创建者的用户ID
|
||||
optional string room_property = 3; // 房间属性
|
||||
optional int32 _timestamp = 4; //时间戳毫秒(相对于房间创建时间时)
|
||||
optional int32 create_time = 5; //创建时间
|
||||
optional int32 max_player = 6; //最大玩家数
|
||||
optional int32 allow_merge = 7 [default = 0]; //允许合并 0:否 1:是
|
||||
repeated MFRoomRobotInfo robot_list = 8; //机器人列表 size() == max_player
|
||||
optional int32 started = 9; //0:游戏未开始 1:游戏已经开始
|
||||
optional int32 reset_count = 10; //房间重置次数 创建时为0之后每重置一次++
|
||||
optional int32 join_over = 11; //0:允许加入 1:不允许加入
|
||||
optional int32 auto_exit_room = 12; //连接断开后自动退房间 0:不退(还可以重连) 1:自动退(无法重连只能再走一次加房间流程)
|
||||
}
|
||||
|
||||
//创建房间信息
|
||||
message MFCreateRoomInfo
|
||||
{
|
||||
optional string room_name = 1; //房间名
|
||||
optional int32 max_player = 2; //最大玩家数
|
||||
optional int32 allow_merge = 3 [default = 0]; //允许合并 0:否 1:是
|
||||
optional int32 auto_exit_room = 4; //连接断开后自动退房间 0:不退(还可以重连) 1:自动退(无法重连只能再走一次加房间流程)
|
||||
}
|
||||
|
||||
//帧事件项
|
||||
message MFFrameItem
|
||||
{
|
||||
optional string src_account_id = 1; //用户id
|
||||
optional string custom_data = 2; //透传数据
|
||||
optional double timestamp = 3; //时间戳(单位毫秒)
|
||||
}
|
||||
|
||||
//帧事件
|
||||
message MFFrameEventInfo
|
||||
{
|
||||
optional int32 frame_index = 1; //帧序号
|
||||
repeated MFFrameItem frame_items = 2; //同步帧内的数据包数组
|
||||
optional int32 frame_wait_count = 3; //同步帧内的数据包数组数量
|
||||
}
|
||||
|
||||
//-------------------------mfxxx end---------------------------------------
|
||||
|
||||
//登录
|
||||
message CMLogin
|
||||
{
|
||||
optional string account_id = 1; //用户id
|
||||
optional string session_id = 2; //session_id
|
||||
optional string game_id = 3; //game_id
|
||||
optional string device_id = 4; //device_id
|
||||
}
|
||||
message SMLogin
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: 失败
|
||||
optional string room_id = 2; //房间id
|
||||
}
|
||||
|
||||
//心跳
|
||||
message CMPing
|
||||
{
|
||||
}
|
||||
message SMPing
|
||||
{
|
||||
}
|
||||
|
||||
//创建房间
|
||||
message CMCreateRoom
|
||||
{
|
||||
optional MFCreateRoomInfo create_room_info = 1; //创建房间信息
|
||||
optional string user_profile = 2; //玩家简介,可以填写昵称、段位等信息
|
||||
}
|
||||
message SMCreateRoom
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: 失败 -2: 未登录 -3: 已在房间 -4:正在匹配
|
||||
optional string room_id = 2; //房间id
|
||||
optional int32 create_time = 5; //创建时间
|
||||
optional MFRoomInfo room_info = 6; //房间信息
|
||||
}
|
||||
|
||||
//加入随机房间
|
||||
message CMJoinRandomRoom
|
||||
{
|
||||
optional int32 max_player = 1; //最大玩家数
|
||||
optional string user_profile = 2; //玩家简介,可以填写昵称、段位等信息
|
||||
}
|
||||
message SMJoinRandomRoom
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -3: 已在房间 -4:正在匹配
|
||||
optional MFRoomInfo room_info = 2; //房间信息
|
||||
repeated MFRoomUserInfo room_user_info_list = 3; //房间用户信息列表
|
||||
}
|
||||
|
||||
//加入指定房间
|
||||
message CMJoinRoom
|
||||
{
|
||||
optional string room_id = 1; //房间id
|
||||
optional string user_profile = 2; //玩家简介,可以填写昵称、段位等信息
|
||||
}
|
||||
message SMJoinRoom
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: 房间id不存在 -2: 房间已满 -3: 已在房间 -4:正在匹配 -5:无法加入
|
||||
optional MFRoomInfo room_info = 2; //房间信息
|
||||
repeated MFRoomUserInfo room_user_info_list = 3; //房间用户信息列表
|
||||
}
|
||||
|
||||
//加入房间通知
|
||||
message SMRoomPeerJoinNotify
|
||||
{
|
||||
optional MFRoomUserInfo room_user_info = 1; //房间用户信息
|
||||
}
|
||||
|
||||
//踢人
|
||||
message CMKickPlayer
|
||||
{
|
||||
optional string account_id = 1; //用户id
|
||||
optional string custom_data = 2; //透传数据
|
||||
}
|
||||
message SMKickPlayer
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: 失败
|
||||
optional string account_id = 2; //用户id
|
||||
optional string custom_data = 3; //透传数据
|
||||
}
|
||||
|
||||
//踢人notify
|
||||
message SMRoomKickPlayerNotify
|
||||
{
|
||||
optional string account_id = 1; //用户id
|
||||
optional string custom_data = 2; //透传数据
|
||||
optional string owner_id = 3; //房主
|
||||
}
|
||||
|
||||
//加入房间结束
|
||||
message CMJoinOver
|
||||
{
|
||||
optional string custom_data = 1; //自定义内容
|
||||
}
|
||||
message SMJoinOver
|
||||
{
|
||||
optional MFOptResult result = 1; //
|
||||
optional string custom_data = 2; //自定义内容
|
||||
}
|
||||
|
||||
//加入房间结束notify
|
||||
message SMJoinOverNotify
|
||||
{
|
||||
optional string custom_data = 1; //透传数据
|
||||
}
|
||||
|
||||
//加入房间开启
|
||||
message CMJoinOpen
|
||||
{
|
||||
optional string custom_data = 1; //自定义内容
|
||||
}
|
||||
message SMJoinOpen
|
||||
{
|
||||
optional MFOptResult result = 1; //
|
||||
optional string custom_data = 2; //自定义内容
|
||||
}
|
||||
|
||||
//加入房间开启notify
|
||||
message SMJoinOpenNotify
|
||||
{
|
||||
optional string custom_data = 1; //自定义内容
|
||||
}
|
||||
|
||||
//游戏开始
|
||||
message CMGameStart
|
||||
{
|
||||
optional string custom_data = 1; //自定义内容
|
||||
}
|
||||
message SMGameStart
|
||||
{
|
||||
optional MFOptResult result = 1; //
|
||||
optional string custom_data = 2; //自定义内容
|
||||
}
|
||||
|
||||
//发送房间数据
|
||||
message CMSendRoomEvent
|
||||
{
|
||||
optional string custom_data = 1; //消息内容
|
||||
}
|
||||
message SMSendRoomEvent
|
||||
{
|
||||
optional MFOptResult result = 1; //
|
||||
optional string event_id = 2; //事件唯一id
|
||||
optional string custom_data = 3; //消息内容
|
||||
optional int32 _timestamp = 4; //时间戳毫秒(相对于房间创建时间时)
|
||||
}
|
||||
|
||||
//房间数据同步消息
|
||||
message SMRoomEventNotify
|
||||
{
|
||||
optional string src_account_id = 1; //事件发起者id
|
||||
optional string event_id = 2; //事件唯一id
|
||||
optional string custom_data = 3; //消息内容
|
||||
optional int32 _timestamp = 4; //时间戳毫秒(相对于房间创建时间时)
|
||||
}
|
||||
|
||||
//发送离开房间
|
||||
message CMLeaveRoom
|
||||
{
|
||||
optional string custom_data = 1; //自定义内容
|
||||
}
|
||||
message SMLeaveRoom
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: 未在房间中
|
||||
optional string room_id = 2; //房间id
|
||||
optional string account_id = 3; //用户id
|
||||
optional string custom_data = 4; //消息内容
|
||||
}
|
||||
|
||||
//设置房间参数
|
||||
message CMSetRoomParam
|
||||
{
|
||||
optional int32 allow_merge = 1 [default = 0]; //允许合并 0:否 1:是
|
||||
}
|
||||
message SMSetRoomParam
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 其他失败
|
||||
}
|
||||
|
||||
//重置房间状态 当前重置的信息:started 帧事件相关所有字段
|
||||
//注意帧率会改为0客户端需要再次设置帧率会开启新的帧事件
|
||||
message CMResetRoom
|
||||
{
|
||||
optional int32 join_over = 1; //重置后 0:允许加入 1:不允许加入
|
||||
}
|
||||
message SMResetRoom
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 其他失败
|
||||
}
|
||||
|
||||
//强退房间(客户端matchvs sdk层实现,应用层不要主动调用该方法)
|
||||
//客户端记录最后一次进入的房间,当玩家要进入或者创建新的房间时候应主动发CMForceExitRoom消息退上一个房间
|
||||
message CMForceExitRoom
|
||||
{
|
||||
optional string room_id = 1; //房间id
|
||||
}
|
||||
|
||||
//其他人离开房间通知
|
||||
message SMRoomPeerLeaveNotify
|
||||
{
|
||||
optional string room_id = 1; //房间id
|
||||
optional MFRoomUserInfo room_user_info = 2; //房间用户信息
|
||||
optional int32 reason = 3; //0:主动退出 1:断线自动退房(auto_exit_room) 2:CMForceExitRoom
|
||||
}
|
||||
|
||||
//设置同步帧率
|
||||
message CMSetFrameSync
|
||||
{
|
||||
optional int32 frame_rate = 1; //每秒钟同步的帧数
|
||||
}
|
||||
message SMSetFrameSync
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1: frame_rate大于30
|
||||
optional int32 frame_rate = 2; //每秒钟同步的帧数
|
||||
}
|
||||
|
||||
//发送帧事件
|
||||
message CMSendFrameEvent
|
||||
{
|
||||
optional string custom_data = 1; //透传
|
||||
}
|
||||
message SMSendFrameEvent
|
||||
{
|
||||
optional MFOptResult result = 1; //操作结果
|
||||
}
|
||||
|
||||
//断线重连
|
||||
message CMReConnect
|
||||
{
|
||||
optional string room_id = 1; //房间
|
||||
optional string account_id = 2; //用户id
|
||||
optional string session_id = 3; //session_id
|
||||
}
|
||||
message SMReConnect
|
||||
{
|
||||
optional MFOptResult result = 1; //error_code 0: 成功 -1:session已失效 -2:房间不存在 -3
|
||||
optional MFRoomInfo room_info = 2; //房间信息
|
||||
repeated MFRoomUserInfo room_user_info_list = 3; //房间用户信息列表
|
||||
}
|
||||
|
||||
//房间内玩家收到帧同步的回调
|
||||
//注意 需要在客户端保持帧同步的状态,服务器不会保存
|
||||
message SMFrameEventUpdateNotify
|
||||
{
|
||||
optional MFFrameEventInfo frame_info = 1; //帧信息
|
||||
}
|
||||
|
||||
//房间解散通知
|
||||
message SMRoomDisbandNotify
|
||||
{
|
||||
optional string room_id = 1; //房间id
|
||||
}
|
||||
|
||||
//房间合并通知 source 并入 target
|
||||
message SMRoomMergeNotify
|
||||
{
|
||||
optional string source_room_id = 1; //源房间id
|
||||
optional MFRoomInfo target_room_info = 2; //房间信息
|
||||
repeated MFRoomUserInfo target_room_user_info_list = 3; //房间用户信息列表
|
||||
}
|
||||
|
||||
//房间重置通知
|
||||
message SMRoomResetNotify
|
||||
{
|
||||
optional MFRoomInfo room_info = 1; //房间信息
|
||||
repeated MFRoomUserInfo room_user_info_list = 2; //房间用户信息列表
|
||||
}
|
||||
|
||||
//网络状态
|
||||
message SMNetWorkStateNotify
|
||||
{
|
||||
optional string room_id = 1; //房间id
|
||||
optional string account_id = 2; //账号
|
||||
optional int32 state = 3; //网络断开状态 1-网络异常,正在重连 2-重连成功 3-重连失败,退出房间
|
||||
optional string owner = 4; //房主id
|
||||
}
|
66
fc2/images/messages.proto
Normal file
@ -0,0 +1,66 @@
|
||||
//syntax = "proto2";
|
||||
package kingsomevs;
|
||||
|
||||
//消息id定义
|
||||
enum CMMessageId_e
|
||||
{
|
||||
_CMBegin_ = 1;
|
||||
|
||||
_CMPing = 50;
|
||||
|
||||
_CMLogin = 100;
|
||||
_CMCreateRoom = 101;
|
||||
_CMJoinRandomRoom = 102;
|
||||
_CMJoinRoom = 103;
|
||||
_CMKickPlayer = 104;
|
||||
_CMJoinOver = 105;
|
||||
_CMJoinOpen = 106;
|
||||
_CMGameStart = 107;
|
||||
_CMSendRoomEvent = 108;
|
||||
_CMLeaveRoom = 109;
|
||||
_CMSetRoomParam = 110;
|
||||
_CMResetRoom = 111;
|
||||
_CMForceExitRoom = 112;
|
||||
_CMSetFrameSync = 120;
|
||||
_CMSendFrameEvent = 121;
|
||||
_CMReConnect = 130;
|
||||
|
||||
_CMEnd_ = 2000;
|
||||
}
|
||||
|
||||
enum SMMessageId_e
|
||||
{
|
||||
_SMBegin_ = 1;
|
||||
|
||||
_SMPing = 50;
|
||||
|
||||
_SMLogin = 100;
|
||||
_SMCreateRoom = 101;
|
||||
_SMJoinRandomRoom = 102;
|
||||
_SMJoinRoom = 103;
|
||||
_SMKickPlayer = 104;
|
||||
_SMJoinOver = 105;
|
||||
_SMJoinOpen = 106;
|
||||
_SMGameStart = 107;
|
||||
_SMSendRoomEvent = 108;
|
||||
_SMLeaveRoom = 109;
|
||||
_SMSetRoomParam = 110;
|
||||
_SMResetRoom = 111;
|
||||
_SMSetFrameSync = 120;
|
||||
_SMSendFrameEvent = 121;
|
||||
_SMReConnect = 130;
|
||||
|
||||
_SMRoomPeerJoinNotify = 200;
|
||||
_SMRoomKickPlayerNotify = 201;
|
||||
_SMRoomEventNotify = 202;
|
||||
_SMRoomPeerLeaveNotify = 203;
|
||||
_SMRoomDisbandNotify = 204;
|
||||
_SMJoinOverNotify = 205;
|
||||
_SMJoinOpenNotify = 206;
|
||||
_SMRoomMergeNotify = 207;
|
||||
_SMRoomResetNotify = 208;
|
||||
_SMFrameEventUpdateNotify = 210;
|
||||
_SMNetWorkStateNotify = 220;
|
||||
|
||||
_SMEnd_ = 2000;
|
||||
}
|
BIN
fc2/images/rocker-down.png
Executable file
After Width: | Height: | Size: 28 KiB |
BIN
fc2/images/rocker-left.png
Executable file
After Width: | Height: | Size: 31 KiB |
BIN
fc2/images/rocker-up.png
Executable file
After Width: | Height: | Size: 32 KiB |
BIN
fc2/images/rocker.png
Executable file
After Width: | Height: | Size: 27 KiB |
BIN
fc2/images/rockerLeftDown.png
Executable file
After Width: | Height: | Size: 30 KiB |
BIN
fc2/images/rockerLeftUp.png
Executable file
After Width: | Height: | Size: 32 KiB |
67
fc2/index.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=750,minimum-scale=0.4,maximum-scale=1,user-scalable=no">
|
||||
<title></title>
|
||||
<link href="css/fc.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="js/jquery.js"></script>
|
||||
<script type="text/javascript" src="js/pako.js"></script>
|
||||
<script type="text/javascript" src="js/nes-embed.js"></script>
|
||||
<script type="text/javascript" src="js/nes-worker.js"></script>
|
||||
<script type="text/javascript" src="js/js.cookie.js"></script>
|
||||
<script type="text/javascript" src="js/options.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<div id="emulator"></div>
|
||||
</div>
|
||||
<div class='background-div'></div>
|
||||
<canvas id="screen" width="256" height="240"></canvas>
|
||||
<div class="" id="menu_btn">选项</div>
|
||||
<div id="ctrl-wrap" class="ctrl-wrap real">
|
||||
<div class="dire-wrap">
|
||||
<div class="dire-ctrl">
|
||||
<div class="dc-centre"></div>
|
||||
</div>
|
||||
<div class="dc-dire"></div>
|
||||
</div>
|
||||
<div class="key-wrap">
|
||||
<div class="key-btn kay-A" data-key="A">A</div>
|
||||
<div class="key-btn kay-B" data-key="B">B</div>
|
||||
<div class="key-btn kay-AB" data-key="AB">AB</div>
|
||||
<div class="key-btn kay-SE flat-btn" data-key="SE">选择</div>
|
||||
<div class="key-btn kay-ST flat-btn" data-key="ST">开始</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='info-mask' id='info-mask' style="display: none;"></div>
|
||||
<div class="menu-back" id="menu_back">
|
||||
</div>
|
||||
<div class="menu-container hidden" id="menu_container">
|
||||
<div class="menu-title">选项</div>
|
||||
<div class="menu-body">
|
||||
<div class="row">
|
||||
<div class="row-title">按键类型</div>
|
||||
<div class="row-body">
|
||||
<div class="row-part">
|
||||
<input type="radio" value="O" id="radio0" name="key_type" class="md-radiobtn">摇杆
|
||||
</div>
|
||||
<div class="row-part">
|
||||
<input type="radio" value="N" id="radio1" name="key_type" class="md-radiobtn">按键
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-footer">
|
||||
<div class="left-part"></div>
|
||||
<div class="right-part">
|
||||
<div class="menu-btn" id="cancel_btn">取消</div>
|
||||
<div class="menu-btn btn-success" id="save_btn">确定</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
2
fc2/js/jquery.js
vendored
Normal file
163
fc2/js/js.cookie.js
Normal file
@ -0,0 +1,163 @@
|
||||
/*!
|
||||
* JavaScript Cookie v2.2.0
|
||||
* https://github.com/js-cookie/js-cookie
|
||||
*
|
||||
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
|
||||
* Released under the MIT license
|
||||
*/
|
||||
;(function (factory) {
|
||||
var registeredInModuleLoader;
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
registeredInModuleLoader = true;
|
||||
}
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
registeredInModuleLoader = true;
|
||||
}
|
||||
if (!registeredInModuleLoader) {
|
||||
var OldCookies = window.Cookies;
|
||||
var api = window.Cookies = factory();
|
||||
api.noConflict = function () {
|
||||
window.Cookies = OldCookies;
|
||||
return api;
|
||||
};
|
||||
}
|
||||
}(function () {
|
||||
function extend () {
|
||||
var i = 0;
|
||||
var result = {};
|
||||
for (; i < arguments.length; i++) {
|
||||
var attributes = arguments[ i ];
|
||||
for (var key in attributes) {
|
||||
result[key] = attributes[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function decode (s) {
|
||||
return s.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent);
|
||||
}
|
||||
|
||||
function init (converter) {
|
||||
function api() {}
|
||||
|
||||
function set (key, value, attributes) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes = extend({
|
||||
path: '/'
|
||||
}, api.defaults, attributes);
|
||||
|
||||
if (typeof attributes.expires === 'number') {
|
||||
attributes.expires = new Date(new Date() * 1 + attributes.expires * 864e+5);
|
||||
}
|
||||
|
||||
// We're using "expires" because "max-age" is not supported by IE
|
||||
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
|
||||
|
||||
try {
|
||||
var result = JSON.stringify(value);
|
||||
if (/^[\{\[]/.test(result)) {
|
||||
value = result;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
value = converter.write ?
|
||||
converter.write(value, key) :
|
||||
encodeURIComponent(String(value))
|
||||
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
|
||||
|
||||
key = encodeURIComponent(String(key))
|
||||
.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent)
|
||||
.replace(/[\(\)]/g, escape);
|
||||
|
||||
var stringifiedAttributes = '';
|
||||
for (var attributeName in attributes) {
|
||||
if (!attributes[attributeName]) {
|
||||
continue;
|
||||
}
|
||||
stringifiedAttributes += '; ' + attributeName;
|
||||
if (attributes[attributeName] === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Considers RFC 6265 section 5.2:
|
||||
// ...
|
||||
// 3. If the remaining unparsed-attributes contains a %x3B (";")
|
||||
// character:
|
||||
// Consume the characters of the unparsed-attributes up to,
|
||||
// not including, the first %x3B (";") character.
|
||||
// ...
|
||||
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
|
||||
}
|
||||
|
||||
return (document.cookie = key + '=' + value + stringifiedAttributes);
|
||||
}
|
||||
|
||||
function get (key, json) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var jar = {};
|
||||
// To prevent the for loop in the first place assign an empty array
|
||||
// in case there are no cookies at all.
|
||||
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
var i = 0;
|
||||
|
||||
for (; i < cookies.length; i++) {
|
||||
var parts = cookies[i].split('=');
|
||||
var cookie = parts.slice(1).join('=');
|
||||
|
||||
if (!json && cookie.charAt(0) === '"') {
|
||||
cookie = cookie.slice(1, -1);
|
||||
}
|
||||
|
||||
try {
|
||||
var name = decode(parts[0]);
|
||||
cookie = (converter.read || converter)(cookie, name) ||
|
||||
decode(cookie);
|
||||
|
||||
if (json) {
|
||||
try {
|
||||
cookie = JSON.parse(cookie);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
jar[name] = cookie;
|
||||
|
||||
if (key === name) {
|
||||
break;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return key ? jar[key] : jar;
|
||||
}
|
||||
|
||||
api.set = set;
|
||||
api.get = function (key) {
|
||||
return get(key, false /* read as raw */);
|
||||
};
|
||||
api.getJSON = function (key) {
|
||||
return get(key, true /* read as json */);
|
||||
};
|
||||
api.remove = function (key, attributes) {
|
||||
set(key, '', extend(attributes, {
|
||||
expires: -1
|
||||
}));
|
||||
};
|
||||
|
||||
api.defaults = {};
|
||||
|
||||
api.withConverter = init;
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
return init(function () {});
|
||||
}));
|
2
fc2/js/jsnes.min.js
vendored
Normal file
373
fc2/js/nes-embed.js
Normal file
@ -0,0 +1,373 @@
|
||||
var SCREEN_WIDTH = 256;
|
||||
var SCREEN_HEIGHT = 240;
|
||||
|
||||
var canvas_ctx, image;
|
||||
var framebuffer_u8, framebuffer_u32;
|
||||
|
||||
var SAMPLE_COUNT = 4 * 1024;
|
||||
var SAMPLE_MASK = SAMPLE_COUNT - 1;
|
||||
var audio_samples_L = new Float32Array(SAMPLE_COUNT);
|
||||
var audio_samples_R = new Float32Array(SAMPLE_COUNT);
|
||||
|
||||
|
||||
function getParameter(t) {
|
||||
var e = window.location.search,
|
||||
i = new RegExp(t + "=([^&?]*)", "ig");
|
||||
return e.match(i) ? e.match(i)[0].substr(t.length + 1) : null
|
||||
}
|
||||
|
||||
var netmode = !!getParameter("roomId");
|
||||
var gameId = getParameter("id");
|
||||
|
||||
var worker = new Worker('./js/nes-worker.js');
|
||||
worker.postMessage({
|
||||
f: "netmode",
|
||||
data: netmode
|
||||
});
|
||||
|
||||
|
||||
var KEY_MAP = {
|
||||
ST: 13,
|
||||
SE: 9,
|
||||
A: 65,
|
||||
B: 83,
|
||||
Y: 50,
|
||||
X: 49,
|
||||
AB: [65, 83],
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
LEFT: 37,
|
||||
RIGHT: 39,
|
||||
L_D: [37, 40],
|
||||
L_U: [37, 38],
|
||||
R_D: [39, 40],
|
||||
R_U: [39, 38]
|
||||
};
|
||||
var player = 1;
|
||||
window.vm = {
|
||||
oldDire: null,
|
||||
keyEvent: function (t, e, i) {
|
||||
var r = KEY_MAP[t];
|
||||
Array.isArray(r) ? (vm.simulateKeyPress(r[0], e), vm.simulateKeyPress(r[1], e)) : vm.simulateKeyPress(r, e)
|
||||
},
|
||||
initCtrl: function () {
|
||||
var s;
|
||||
vm.initGameDireCtrl(function (t, e) {
|
||||
var i = -1 == t ? s : ["DOWN", "L_D", "LEFT", "L_U", "UP", "R_U", "RIGHT", "R_D"][t],
|
||||
r = -1 == t;
|
||||
s && (s != i || r) && vm.keyEvent(s, "keyup", e), s = i, !r && vm.keyEvent(i, "keydown", e)
|
||||
}), vm.initGameKeyCtrl(), $(".ctrl-wrap").on("click contextmenu touchstart touchmove touchend touchcancel tap", function (t) {
|
||||
return t.stopPropagation(), t.preventDefault(), !1
|
||||
})
|
||||
},
|
||||
initGameDireCtrl: function (u) {
|
||||
var d = this,
|
||||
t = $(".dire-wrap"),
|
||||
p = $(".dc-centre"),
|
||||
e = $(".dire-ctrl"),
|
||||
l = $(".dc-dire"),
|
||||
i = e.offset(),
|
||||
n = i.top,
|
||||
a = i.left,
|
||||
f = p.width() / 2,
|
||||
m = e.width() / 2,
|
||||
r = function () {
|
||||
p.css({
|
||||
transform: "translate3d(83px,83px,0)"
|
||||
}), p.css("opacity", "1"), l.css("opacity", "0")
|
||||
},
|
||||
y = function (t, e) {
|
||||
var i = t[e];
|
||||
if (!i) return {
|
||||
x: 160,
|
||||
y: 500
|
||||
};
|
||||
var r = i.pageX - a,
|
||||
s = i.pageY - n;
|
||||
return 600 < s ? y(t, e + 1) : {
|
||||
x: r,
|
||||
y: s
|
||||
}
|
||||
};
|
||||
r(), t.on("touchstart touchmove", function (t) {
|
||||
var e, i, r = y(t.touches, 0),
|
||||
s = r.x,
|
||||
n = r.y,
|
||||
a = Math.atan2(n - m, s - m) / (Math.PI / 180);
|
||||
if (l.css("transform", "rotate(" + a + "deg)"), d.onCtrlDire(a, u), (e = m - s, i = m - n, Math.sqrt(e * e + i * i)) <= 86) {
|
||||
var o = s - f,
|
||||
h = n - f;
|
||||
p.css("transform", "translate3d(" + o + "px," + h + "px,0)")
|
||||
} else {
|
||||
var c = a * Math.PI / 180;
|
||||
o = m + 86 * Math.cos(c) - f, h = m + 86 * Math.sin(c) - f;
|
||||
p.css("transform", "translate3d(" + o + "px," + h + "px,0)")
|
||||
}
|
||||
"touchstart" === t.type && (p.css("opacity", "0.8"), l.css("opacity", "0.6"))
|
||||
}).on("touchend", function () {
|
||||
r(), u(-1, 0), d.oldDire = -1
|
||||
})
|
||||
},
|
||||
initGameKeyCtrl: function () {
|
||||
var s = this;
|
||||
$(".key-btn").bind("touchstart touchend", function (t) {
|
||||
var e = $(t.target),
|
||||
i = e.attr("data-key"),
|
||||
r = "touchstart" === t.type;
|
||||
s.keyEvent(i, r ? "keydown" : "keyup", 0), e[r ? "addClass" : "removeClass"]("btn-on")
|
||||
})
|
||||
},
|
||||
onCtrlDire: function (t, e) {
|
||||
this.onCtrlDire;
|
||||
var i;//= Math.round((t + 180) / 45) % 8;
|
||||
var abst = Math.abs(t);
|
||||
if ((abst >= 35 && abst <= 55) || (abst >= 125 && abst <= 145)) {
|
||||
i = Math.round((t + 180) / 45) % 8;
|
||||
} else {
|
||||
i = Math.round((t + 180) / 90) % 4 * 2;
|
||||
}
|
||||
this.oldDire != i && (e(i, t), this.oldDire = i)
|
||||
},
|
||||
simulateKeyPress: function (t, e) {
|
||||
if (!netmode) {
|
||||
worker.postMessage({
|
||||
f: "keypad",
|
||||
data: {keyCode: t, action: e}
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
var data = {
|
||||
m: NetEnum.JUMP,
|
||||
v: {
|
||||
keyCode: t,
|
||||
keyaction: e,
|
||||
player: player
|
||||
}
|
||||
}
|
||||
NetWorkManage.sendFrameEvent(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function startGame() {
|
||||
var path = "https://h5games-al.kingsome.cn/emulator-static/roms/" + gameId + ".nes"
|
||||
nes_load_url("screen", path)
|
||||
vm.initCtrl()
|
||||
|
||||
if (netmode) {
|
||||
var roomId = getParameter("roomId")
|
||||
NetWorkManage.initengine();
|
||||
NetWorkManage.cbOnLogin = function (res) {
|
||||
if (res.status === 0) {
|
||||
console.log(res)
|
||||
NetWorkManage.cbOnLogin = null;
|
||||
if (roomId === "0") {
|
||||
NetWorkManage.engine.createRoom('testroom', 2, "");
|
||||
} else {
|
||||
player = 2;
|
||||
NetWorkManage.engine.joinRoom(roomId, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
NetWorkManage.cbOnJoinRoom = function (res) {
|
||||
NetWorkManage.gameStart();
|
||||
};
|
||||
var updateframe = function () {
|
||||
if (NetWorkManage.frames.length > 0) {
|
||||
var fr = NetWorkManage.frames.shift();
|
||||
if (fr.isnet) {
|
||||
NetWorkHandle.processFrame(fr);
|
||||
}
|
||||
worker.postMessage({f: "updateframe"});
|
||||
if (fr.frameIndex < NetWorkManage.frameIndex) {
|
||||
updateframe();
|
||||
}
|
||||
}
|
||||
}
|
||||
setInterval(function () {
|
||||
updateframe();
|
||||
}, 1000 / 60)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function checkSession_id(res) {
|
||||
try {
|
||||
if (JSON.parse(res.target.response).errcode === 0) {
|
||||
startGame();
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
var noCheck = getParameter('nt');
|
||||
if (noCheck) {
|
||||
startGame()
|
||||
} else {
|
||||
var account_id = getParameter("account_id")
|
||||
var session_id = getParameter("session_id")
|
||||
var url;
|
||||
if (getParameter("version") === "product") {
|
||||
url = "https://login.kingsome.cn/webapp/index.php?c=Login&a=sessionAuth"
|
||||
} else {
|
||||
url = "https://login-test.kingsome.cn/webapp/index.php?c=Login&a=sessionAuth"
|
||||
}
|
||||
url += "&account_id=";
|
||||
url += account_id;
|
||||
url += "&session_id=";
|
||||
url += session_id;
|
||||
|
||||
var ajax = new XMLHttpRequest();
|
||||
ajax.onload = checkSession_id;
|
||||
ajax.open("GET", url, true);
|
||||
ajax.send(null);
|
||||
}
|
||||
}
|
||||
|
||||
var script_processor = null
|
||||
var audio_ctx = null
|
||||
|
||||
function initAudioEvent() {
|
||||
script_processor && (script_processor.disconnect(audio_ctx.destination), script_processor.onaudioprocess = null, script_processor = null)
|
||||
audio_ctx && audio_ctx.close().catch(handleError)
|
||||
window.AudioContext = window.webkitAudioContext || window.AudioContext
|
||||
audio_ctx = new window.AudioContext();
|
||||
script_processor = audio_ctx.createScriptProcessor(4096, 0, 2);
|
||||
script_processor.onaudioprocess = audio_callback;
|
||||
script_processor.connect(audio_ctx.destination);
|
||||
|
||||
$(".key-btn").unbind("touchstart", initAudioEvent)
|
||||
|
||||
}
|
||||
|
||||
function audio_callback(e) {
|
||||
var dst = e.outputBuffer;
|
||||
var len = dst.length;
|
||||
var dst_l = dst.getChannelData(0);
|
||||
var dst_r = dst.getChannelData(1);
|
||||
for (var i = 0; i < len; i++) {
|
||||
var src_idx = (audio_read_cursor + i) & SAMPLE_MASK;
|
||||
dst_l[i] = audio_samples_L[src_idx];
|
||||
dst_r[i] = audio_samples_R[src_idx];
|
||||
}
|
||||
|
||||
audio_read_cursor = (audio_read_cursor + len) & SAMPLE_MASK;
|
||||
}
|
||||
|
||||
function nes_init(canvas_id) {
|
||||
var canvas = document.getElementById(canvas_id);
|
||||
canvas_ctx = canvas.getContext("2d");
|
||||
image = canvas_ctx.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
canvas_ctx.fillStyle = "black";
|
||||
canvas_ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
var buffer = new ArrayBuffer(image.data.length);
|
||||
|
||||
worker.postMessage({
|
||||
f: "setBuffer",
|
||||
data: image.data.length
|
||||
});
|
||||
|
||||
|
||||
framebuffer_u8 = new Uint8ClampedArray(buffer);
|
||||
framebuffer_u32 = new Uint32Array(buffer);
|
||||
$(".key-btn").bind("touchstart", initAudioEvent)
|
||||
}
|
||||
|
||||
function nes_boot(rom_data) {
|
||||
worker.postMessage({
|
||||
f: "nesloadROM",
|
||||
data: rom_data
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function nes_load_url(canvas_id, path) {
|
||||
nes_init(canvas_id);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", path);
|
||||
req.overrideMimeType("text/plain; charset=x-user-defined");
|
||||
req.onerror = () => console.log(`Error loading ${path}: ${req.statusText}`);
|
||||
|
||||
req.onload = function () {
|
||||
if (this.status === 200) {
|
||||
nes_boot(this.responseText);
|
||||
} else if (this.status === 0) {
|
||||
// Aborted, so ignore error
|
||||
} else {
|
||||
req.onerror();
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
}
|
||||
|
||||
worker.onmessage = function (event) {
|
||||
if (event.data.f === "frame") {
|
||||
// framebuffer_u8 = event.data.data
|
||||
image.data.set(event.data.data.framebuffer_u8);
|
||||
canvas_ctx.putImageData(image, 0, 0);
|
||||
|
||||
audio_samples_L = event.data.data.audio_samples_L
|
||||
audio_samples_R = event.data.data.audio_samples_R
|
||||
audio_read_cursor = event.data.data.audio_read_cursor
|
||||
} else if (event.data.f === 'saveData') {
|
||||
let data = zip(event.data.data);
|
||||
console.log(data);
|
||||
localStorage.setItem(gameId, data);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
console.log(event.keyCode);
|
||||
worker.postMessage({
|
||||
f: "keypad",
|
||||
data: {keyCode: event.keyCode, action: 'keydown'}
|
||||
});
|
||||
});
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (event.keyCode === 83) {
|
||||
//save
|
||||
worker.postMessage({
|
||||
f: "save",
|
||||
});
|
||||
}else if (event.keyCode === 82) {
|
||||
//save
|
||||
worker.postMessage({
|
||||
f: "reload",
|
||||
});
|
||||
} else if (event.keyCode === 76) {
|
||||
//load
|
||||
var saveData = localStorage.getItem(gameId);
|
||||
if( saveData == null ) {
|
||||
console.log("nothing to load");
|
||||
alert("请先保存.");
|
||||
return;
|
||||
}
|
||||
let unzipData = unzip(saveData);
|
||||
var decodedData = JSON.parse(unzipData);
|
||||
worker.postMessage({
|
||||
f: "load",
|
||||
data: decodedData
|
||||
});
|
||||
}
|
||||
worker.postMessage({
|
||||
f: "keypad",
|
||||
data: {keyCode: event.keyCode, action: 'keyup'}
|
||||
});
|
||||
});
|
||||
|
||||
function unzip(binaryString) {
|
||||
return unescape(pako.inflate(binaryString, { to: 'string' }));
|
||||
}
|
||||
|
||||
// 压缩
|
||||
function zip(str) {
|
||||
//escape(str) --->压缩前编码,防止中午乱码
|
||||
var binaryString = pako.gzip(escape(str), {to: 'string'});
|
||||
return binaryString;
|
||||
}
|
158
fc2/js/nes-worker.js
Normal file
@ -0,0 +1,158 @@
|
||||
if('function' === typeof importScripts) {
|
||||
importScripts('./jsnes.min.js');
|
||||
}
|
||||
var SCREEN_WIDTH = 256;
|
||||
var SCREEN_HEIGHT = 240;
|
||||
var FRAMEBUFFER_SIZE = SCREEN_WIDTH * SCREEN_HEIGHT;
|
||||
var framebuffer_u8, framebuffer_u32;
|
||||
|
||||
var AUDIO_BUFFERING = 512;
|
||||
var SAMPLE_COUNT = 4 * 1024;
|
||||
var SAMPLE_MASK = SAMPLE_COUNT - 1;
|
||||
var audio_samples_L = new Float32Array(SAMPLE_COUNT);
|
||||
var audio_samples_R = new Float32Array(SAMPLE_COUNT);
|
||||
var audio_write_cursor = 0
|
||||
, audio_read_cursor = 0;
|
||||
var netmode = false;
|
||||
|
||||
var nes = new jsnes.NES({
|
||||
onFrame: function(framebuffer_24) {
|
||||
for (var i = 0; i < FRAMEBUFFER_SIZE; i++)
|
||||
framebuffer_u32[i] = 0xFF000000 | framebuffer_24[i];
|
||||
},
|
||||
onAudioSample: function(l, r) {
|
||||
audio_samples_L[audio_write_cursor] = l;
|
||||
audio_samples_R[audio_write_cursor] = r;
|
||||
audio_write_cursor = (audio_write_cursor + 1) & SAMPLE_MASK;
|
||||
},
|
||||
});
|
||||
|
||||
function keyboard(callback, event, p) {
|
||||
var player = p || 1;
|
||||
switch (event.keyCode) {
|
||||
case 38:
|
||||
// UP
|
||||
callback(player, jsnes.Controller.BUTTON_UP);
|
||||
break;
|
||||
case 40:
|
||||
// Down
|
||||
callback(player, jsnes.Controller.BUTTON_DOWN);
|
||||
break;
|
||||
case 37:
|
||||
// Left
|
||||
callback(player, jsnes.Controller.BUTTON_LEFT);
|
||||
break;
|
||||
case 39:
|
||||
// Right
|
||||
callback(player, jsnes.Controller.BUTTON_RIGHT);
|
||||
break;
|
||||
case 65:
|
||||
// 'a' - qwerty, dvorak
|
||||
case 81:
|
||||
// 'q' - azerty
|
||||
callback(player, jsnes.Controller.BUTTON_A);
|
||||
break;
|
||||
case 83:
|
||||
// 's' - qwerty, azerty
|
||||
case 79:
|
||||
// 'o' - dvorak
|
||||
callback(player, jsnes.Controller.BUTTON_B);
|
||||
break;
|
||||
case 9:
|
||||
// Tab
|
||||
callback(player, jsnes.Controller.BUTTON_SELECT);
|
||||
break;
|
||||
case 13:
|
||||
// Return
|
||||
callback(player, jsnes.Controller.BUTTON_START);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
onmessage = function(event) {
|
||||
switch (event.data.f) {
|
||||
case 'nesloadROM':
|
||||
nes.loadROM(event.data.data);
|
||||
|
||||
if (netmode === false) {
|
||||
setInterval(function() {
|
||||
nes.frame();
|
||||
postMessage({
|
||||
f: "frame",
|
||||
data: {
|
||||
framebuffer_u8: framebuffer_u8,
|
||||
audio_samples_L: audio_samples_L,
|
||||
audio_samples_R: audio_samples_R,
|
||||
audio_read_cursor: audio_read_cursor,
|
||||
}
|
||||
});
|
||||
}, 1000 / 60)
|
||||
}
|
||||
break;
|
||||
case 'updateframe':
|
||||
nes.frame();
|
||||
postMessage({
|
||||
f: "frame",
|
||||
data: {
|
||||
framebuffer_u8: framebuffer_u8,
|
||||
audio_samples_L: audio_samples_L,
|
||||
audio_samples_R: audio_samples_R,
|
||||
audio_read_cursor: audio_read_cursor,
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'netmode':
|
||||
netmode = event.data.data;
|
||||
break;
|
||||
case 'setBuffer':
|
||||
var buffer = new ArrayBuffer(event.data.data);
|
||||
framebuffer_u8 = new Uint8ClampedArray(buffer);
|
||||
framebuffer_u32 = new Uint32Array(buffer);
|
||||
break;
|
||||
case 'keypad':
|
||||
if (event.data.data.action === "keydown") {
|
||||
keyboard(nes.buttonDown, event.data.data)
|
||||
} else {
|
||||
keyboard(nes.buttonUp, event.data.data)
|
||||
}
|
||||
break;
|
||||
case 'netkeypad':
|
||||
if (event.data.data.keyaction === "keydown") {
|
||||
keyboard(nes.buttonDown, event.data.data, event.data.data.player)
|
||||
} else {
|
||||
keyboard(nes.buttonUp, event.data.data, event.data.data.player)
|
||||
}
|
||||
break;
|
||||
case 'netframe':
|
||||
nes.frame();
|
||||
postMessage({
|
||||
f: "frame",
|
||||
data: {
|
||||
framebuffer_u8: framebuffer_u8,
|
||||
audio_samples_L: audio_samples_L,
|
||||
audio_samples_R: audio_samples_R,
|
||||
audio_read_cursor: audio_read_cursor,
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'save':
|
||||
var currData = nes.toJSON();
|
||||
var saveData = JSON.stringify(currData);
|
||||
postMessage({
|
||||
f: "saveData",
|
||||
data: saveData
|
||||
});
|
||||
break;
|
||||
case 'load':
|
||||
var decodedData = event.data.data;
|
||||
console.log(decodedData);
|
||||
nes.fromJSON(decodedData);
|
||||
break;
|
||||
case 'reload':
|
||||
nes.reloadROM();
|
||||
break;
|
||||
}
|
||||
//console.log('11Received message ' + event.data);
|
||||
// doSomething();
|
||||
}
|
37
fc2/js/options.js
Normal file
@ -0,0 +1,37 @@
|
||||
$(function () {
|
||||
let keyType = Cookies.get('key_type') || 'N';
|
||||
console.log(`keyType: ${keyType}`);
|
||||
let setKeyType = function (keyType) {
|
||||
$("[name=key_type]").val([keyType]);
|
||||
if (keyType === 'N') {
|
||||
$('#ctrl-wrap').addClass('real');
|
||||
$('#menu_btn').addClass('real');
|
||||
} else {
|
||||
$('#ctrl-wrap').removeClass('real');
|
||||
$('#menu_btn').removeClass('real');
|
||||
}
|
||||
Cookies.set('key_type', keyType);
|
||||
}
|
||||
setKeyType(keyType);
|
||||
$('#menu_btn').click(function () {
|
||||
console.log('menu btn click');
|
||||
showMenu();
|
||||
});
|
||||
let showMenu = function() {
|
||||
$('#menu_back').show();
|
||||
$('#menu_container').removeClass('hidden');
|
||||
}
|
||||
let hideMenu = function() {
|
||||
$('#menu_back').hide();
|
||||
$('#menu_container').addClass('hidden');
|
||||
}
|
||||
|
||||
$('#cancel_btn').click(function () {
|
||||
hideMenu();
|
||||
})
|
||||
$('#save_btn').click(function () {
|
||||
keyType = $('input[name="key_type"]:checked').val();
|
||||
setKeyType(keyType);
|
||||
hideMenu();
|
||||
})
|
||||
})
|
6818
fc2/js/pako.js
Normal file
27
fc2/source/controller.js
Normal file
@ -0,0 +1,27 @@
|
||||
var Controller = function() {
|
||||
this.state = new Array(8);
|
||||
for (var i = 0; i < this.state.length; i++) {
|
||||
this.state[i] = 0x40;
|
||||
}
|
||||
};
|
||||
|
||||
Controller.BUTTON_A = 0;
|
||||
Controller.BUTTON_B = 1;
|
||||
Controller.BUTTON_SELECT = 2;
|
||||
Controller.BUTTON_START = 3;
|
||||
Controller.BUTTON_UP = 4;
|
||||
Controller.BUTTON_DOWN = 5;
|
||||
Controller.BUTTON_LEFT = 6;
|
||||
Controller.BUTTON_RIGHT = 7;
|
||||
|
||||
Controller.prototype = {
|
||||
buttonDown: function(key) {
|
||||
this.state[key] = 0x41;
|
||||
},
|
||||
|
||||
buttonUp: function(key) {
|
||||
this.state[key] = 0x40;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Controller;
|
2024
fc2/source/cpu.js
Normal file
4
fc2/source/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
Controller: require("./controller"),
|
||||
NES: require("./nes")
|
||||
};
|
1518
fc2/source/mappers.js
Normal file
210
fc2/source/nes.js
Normal file
@ -0,0 +1,210 @@
|
||||
var CPU = require("./cpu");
|
||||
var Controller = require("./controller");
|
||||
var PPU = require("./ppu");
|
||||
var PAPU = require("./papu");
|
||||
var ROM = require("./rom");
|
||||
|
||||
var NES = function(opts) {
|
||||
this.opts = {
|
||||
onFrame: function() {},
|
||||
onAudioSample: null,
|
||||
onStatusUpdate: function() {},
|
||||
onBatteryRamWrite: function() {},
|
||||
|
||||
// FIXME: not actually used except for in PAPU
|
||||
preferredFrameRate: 60,
|
||||
|
||||
emulateSound: true,
|
||||
sampleRate: 44100 // Sound sample rate in hz
|
||||
};
|
||||
if (typeof opts !== "undefined") {
|
||||
var key;
|
||||
for (key in this.opts) {
|
||||
if (typeof opts[key] !== "undefined") {
|
||||
this.opts[key] = opts[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.frameTime = 1000 / this.opts.preferredFrameRate;
|
||||
|
||||
this.ui = {
|
||||
writeFrame: this.opts.onFrame,
|
||||
updateStatus: this.opts.onStatusUpdate
|
||||
};
|
||||
this.cpu = new CPU(this);
|
||||
this.ppu = new PPU(this);
|
||||
this.papu = new PAPU(this);
|
||||
this.mmap = null; // set in loadROM()
|
||||
this.controllers = {
|
||||
1: new Controller(),
|
||||
2: new Controller()
|
||||
};
|
||||
|
||||
this.ui.updateStatus("Ready to load a ROM.");
|
||||
|
||||
this.frame = this.frame.bind(this);
|
||||
this.buttonDown = this.buttonDown.bind(this);
|
||||
this.buttonUp = this.buttonUp.bind(this);
|
||||
this.zapperMove = this.zapperMove.bind(this);
|
||||
this.zapperFireDown = this.zapperFireDown.bind(this);
|
||||
this.zapperFireUp = this.zapperFireUp.bind(this);
|
||||
};
|
||||
|
||||
NES.prototype = {
|
||||
fpsFrameCount: 0,
|
||||
romData: null,
|
||||
|
||||
// Resets the system
|
||||
reset: function() {
|
||||
if (this.mmap !== null) {
|
||||
this.mmap.reset();
|
||||
}
|
||||
|
||||
this.cpu.reset();
|
||||
this.ppu.reset();
|
||||
this.papu.reset();
|
||||
|
||||
this.lastFpsTime = null;
|
||||
this.fpsFrameCount = 0;
|
||||
},
|
||||
|
||||
frame: function() {
|
||||
this.ppu.startFrame();
|
||||
var cycles = 0;
|
||||
var emulateSound = this.opts.emulateSound;
|
||||
var cpu = this.cpu;
|
||||
var ppu = this.ppu;
|
||||
var papu = this.papu;
|
||||
FRAMELOOP: for (;;) {
|
||||
if (cpu.cyclesToHalt === 0) {
|
||||
// Execute a CPU instruction
|
||||
cycles = cpu.emulate();
|
||||
if (emulateSound) {
|
||||
papu.clockFrameCounter(cycles);
|
||||
}
|
||||
cycles *= 3;
|
||||
} else {
|
||||
if (cpu.cyclesToHalt > 8) {
|
||||
cycles = 24;
|
||||
if (emulateSound) {
|
||||
papu.clockFrameCounter(8);
|
||||
}
|
||||
cpu.cyclesToHalt -= 8;
|
||||
} else {
|
||||
cycles = cpu.cyclesToHalt * 3;
|
||||
if (emulateSound) {
|
||||
papu.clockFrameCounter(cpu.cyclesToHalt);
|
||||
}
|
||||
cpu.cyclesToHalt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (; cycles > 0; cycles--) {
|
||||
if (
|
||||
ppu.curX === ppu.spr0HitX &&
|
||||
ppu.f_spVisibility === 1 &&
|
||||
ppu.scanline - 21 === ppu.spr0HitY
|
||||
) {
|
||||
// Set sprite 0 hit flag:
|
||||
ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT, true);
|
||||
}
|
||||
|
||||
if (ppu.requestEndFrame) {
|
||||
ppu.nmiCounter--;
|
||||
if (ppu.nmiCounter === 0) {
|
||||
ppu.requestEndFrame = false;
|
||||
ppu.startVBlank();
|
||||
break FRAMELOOP;
|
||||
}
|
||||
}
|
||||
|
||||
ppu.curX++;
|
||||
if (ppu.curX === 341) {
|
||||
ppu.curX = 0;
|
||||
ppu.endScanline();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.fpsFrameCount++;
|
||||
},
|
||||
|
||||
buttonDown: function(controller, button) {
|
||||
this.controllers[controller].buttonDown(button);
|
||||
},
|
||||
|
||||
buttonUp: function(controller, button) {
|
||||
this.controllers[controller].buttonUp(button);
|
||||
},
|
||||
|
||||
zapperMove: function(x, y) {
|
||||
if (!this.mmap) return;
|
||||
this.mmap.zapperX = x;
|
||||
this.mmap.zapperY = y;
|
||||
},
|
||||
|
||||
zapperFireDown: function() {
|
||||
if (!this.mmap) return;
|
||||
this.mmap.zapperFired = true;
|
||||
},
|
||||
|
||||
zapperFireUp: function() {
|
||||
if (!this.mmap) return;
|
||||
this.mmap.zapperFired = false;
|
||||
},
|
||||
|
||||
getFPS: function() {
|
||||
var now = +new Date();
|
||||
var fps = null;
|
||||
if (this.lastFpsTime) {
|
||||
fps = this.fpsFrameCount / ((now - this.lastFpsTime) / 1000);
|
||||
}
|
||||
this.fpsFrameCount = 0;
|
||||
this.lastFpsTime = now;
|
||||
return fps;
|
||||
},
|
||||
|
||||
reloadROM: function() {
|
||||
if (this.romData !== null) {
|
||||
this.loadROM(this.romData);
|
||||
}
|
||||
},
|
||||
|
||||
// Loads a ROM file into the CPU and PPU.
|
||||
// The ROM file is validated first.
|
||||
loadROM: function(data) {
|
||||
// Load ROM file:
|
||||
this.rom = new ROM(this);
|
||||
this.rom.load(data);
|
||||
|
||||
this.reset();
|
||||
this.mmap = this.rom.createMapper();
|
||||
this.mmap.loadROM();
|
||||
this.ppu.setMirroring(this.rom.getMirroringType());
|
||||
this.romData = data;
|
||||
},
|
||||
|
||||
setFramerate: function(rate) {
|
||||
this.opts.preferredFrameRate = rate;
|
||||
this.frameTime = 1000 / rate;
|
||||
this.papu.setSampleRate(this.opts.sampleRate, false);
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return {
|
||||
romData: this.romData,
|
||||
cpu: this.cpu.toJSON(),
|
||||
mmap: this.mmap.toJSON(),
|
||||
ppu: this.ppu.toJSON()
|
||||
};
|
||||
},
|
||||
|
||||
fromJSON: function(s) {
|
||||
this.loadROM(s.romData);
|
||||
this.cpu.fromJSON(s.cpu);
|
||||
this.mmap.fromJSON(s.mmap);
|
||||
this.ppu.fromJSON(s.ppu);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = NES;
|
1371
fc2/source/papu.js
Normal file
1753
fc2/source/ppu.js
Normal file
204
fc2/source/rom.js
Normal file
@ -0,0 +1,204 @@
|
||||
var Mappers = require("./mappers");
|
||||
var Tile = require("./tile");
|
||||
|
||||
var ROM = function(nes) {
|
||||
this.nes = nes;
|
||||
|
||||
this.mapperName = new Array(92);
|
||||
|
||||
for (var i = 0; i < 92; i++) {
|
||||
this.mapperName[i] = "Unknown Mapper";
|
||||
}
|
||||
this.mapperName[0] = "Direct Access";
|
||||
this.mapperName[1] = "Nintendo MMC1";
|
||||
this.mapperName[2] = "UNROM";
|
||||
this.mapperName[3] = "CNROM";
|
||||
this.mapperName[4] = "Nintendo MMC3";
|
||||
this.mapperName[5] = "Nintendo MMC5";
|
||||
this.mapperName[6] = "FFE F4xxx";
|
||||
this.mapperName[7] = "AOROM";
|
||||
this.mapperName[8] = "FFE F3xxx";
|
||||
this.mapperName[9] = "Nintendo MMC2";
|
||||
this.mapperName[10] = "Nintendo MMC4";
|
||||
this.mapperName[11] = "Color Dreams Chip";
|
||||
this.mapperName[12] = "FFE F6xxx";
|
||||
this.mapperName[15] = "100-in-1 switch";
|
||||
this.mapperName[16] = "Bandai chip";
|
||||
this.mapperName[17] = "FFE F8xxx";
|
||||
this.mapperName[18] = "Jaleco SS8806 chip";
|
||||
this.mapperName[19] = "Namcot 106 chip";
|
||||
this.mapperName[20] = "Famicom Disk System";
|
||||
this.mapperName[21] = "Konami VRC4a";
|
||||
this.mapperName[22] = "Konami VRC2a";
|
||||
this.mapperName[23] = "Konami VRC2a";
|
||||
this.mapperName[24] = "Konami VRC6";
|
||||
this.mapperName[25] = "Konami VRC4b";
|
||||
this.mapperName[32] = "Irem G-101 chip";
|
||||
this.mapperName[33] = "Taito TC0190/TC0350";
|
||||
this.mapperName[34] = "32kB ROM switch";
|
||||
|
||||
this.mapperName[64] = "Tengen RAMBO-1 chip";
|
||||
this.mapperName[65] = "Irem H-3001 chip";
|
||||
this.mapperName[66] = "GNROM switch";
|
||||
this.mapperName[67] = "SunSoft3 chip";
|
||||
this.mapperName[68] = "SunSoft4 chip";
|
||||
this.mapperName[69] = "SunSoft5 FME-7 chip";
|
||||
this.mapperName[71] = "Camerica chip";
|
||||
this.mapperName[78] = "Irem 74HC161/32-based";
|
||||
this.mapperName[91] = "Pirate HK-SF3 chip";
|
||||
};
|
||||
|
||||
ROM.prototype = {
|
||||
// Mirroring types:
|
||||
VERTICAL_MIRRORING: 0,
|
||||
HORIZONTAL_MIRRORING: 1,
|
||||
FOURSCREEN_MIRRORING: 2,
|
||||
SINGLESCREEN_MIRRORING: 3,
|
||||
SINGLESCREEN_MIRRORING2: 4,
|
||||
SINGLESCREEN_MIRRORING3: 5,
|
||||
SINGLESCREEN_MIRRORING4: 6,
|
||||
CHRROM_MIRRORING: 7,
|
||||
|
||||
header: null,
|
||||
rom: null,
|
||||
vrom: null,
|
||||
vromTile: null,
|
||||
|
||||
romCount: null,
|
||||
vromCount: null,
|
||||
mirroring: null,
|
||||
batteryRam: null,
|
||||
trainer: null,
|
||||
fourScreen: null,
|
||||
mapperType: null,
|
||||
valid: false,
|
||||
|
||||
load: function(data) {
|
||||
var i, j, v;
|
||||
|
||||
if (data.indexOf("NES\x1a") === -1) {
|
||||
throw new Error("Not a valid NES ROM.");
|
||||
}
|
||||
this.header = new Array(16);
|
||||
for (i = 0; i < 16; i++) {
|
||||
this.header[i] = data.charCodeAt(i) & 0xff;
|
||||
}
|
||||
this.romCount = this.header[4];
|
||||
this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB
|
||||
this.mirroring = (this.header[6] & 1) !== 0 ? 1 : 0;
|
||||
this.batteryRam = (this.header[6] & 2) !== 0;
|
||||
this.trainer = (this.header[6] & 4) !== 0;
|
||||
this.fourScreen = (this.header[6] & 8) !== 0;
|
||||
this.mapperType = (this.header[6] >> 4) | (this.header[7] & 0xf0);
|
||||
/* TODO
|
||||
if (this.batteryRam)
|
||||
this.loadBatteryRam();*/
|
||||
// Check whether byte 8-15 are zero's:
|
||||
var foundError = false;
|
||||
for (i = 8; i < 16; i++) {
|
||||
if (this.header[i] !== 0) {
|
||||
foundError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundError) {
|
||||
this.mapperType &= 0xf; // Ignore byte 7
|
||||
}
|
||||
// Load PRG-ROM banks:
|
||||
this.rom = new Array(this.romCount);
|
||||
var offset = 16;
|
||||
for (i = 0; i < this.romCount; i++) {
|
||||
this.rom[i] = new Array(16384);
|
||||
for (j = 0; j < 16384; j++) {
|
||||
if (offset + j >= data.length) {
|
||||
break;
|
||||
}
|
||||
this.rom[i][j] = data.charCodeAt(offset + j) & 0xff;
|
||||
}
|
||||
offset += 16384;
|
||||
}
|
||||
// Load CHR-ROM banks:
|
||||
this.vrom = new Array(this.vromCount);
|
||||
for (i = 0; i < this.vromCount; i++) {
|
||||
this.vrom[i] = new Array(4096);
|
||||
for (j = 0; j < 4096; j++) {
|
||||
if (offset + j >= data.length) {
|
||||
break;
|
||||
}
|
||||
this.vrom[i][j] = data.charCodeAt(offset + j) & 0xff;
|
||||
}
|
||||
offset += 4096;
|
||||
}
|
||||
|
||||
// Create VROM tiles:
|
||||
this.vromTile = new Array(this.vromCount);
|
||||
for (i = 0; i < this.vromCount; i++) {
|
||||
this.vromTile[i] = new Array(256);
|
||||
for (j = 0; j < 256; j++) {
|
||||
this.vromTile[i][j] = new Tile();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert CHR-ROM banks to tiles:
|
||||
var tileIndex;
|
||||
var leftOver;
|
||||
for (v = 0; v < this.vromCount; v++) {
|
||||
for (i = 0; i < 4096; i++) {
|
||||
tileIndex = i >> 4;
|
||||
leftOver = i % 16;
|
||||
if (leftOver < 8) {
|
||||
this.vromTile[v][tileIndex].setScanline(
|
||||
leftOver,
|
||||
this.vrom[v][i],
|
||||
this.vrom[v][i + 8]
|
||||
);
|
||||
} else {
|
||||
this.vromTile[v][tileIndex].setScanline(
|
||||
leftOver - 8,
|
||||
this.vrom[v][i - 8],
|
||||
this.vrom[v][i]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.valid = true;
|
||||
},
|
||||
|
||||
getMirroringType: function() {
|
||||
if (this.fourScreen) {
|
||||
return this.FOURSCREEN_MIRRORING;
|
||||
}
|
||||
if (this.mirroring === 0) {
|
||||
return this.HORIZONTAL_MIRRORING;
|
||||
}
|
||||
return this.VERTICAL_MIRRORING;
|
||||
},
|
||||
|
||||
getMapperName: function() {
|
||||
if (this.mapperType >= 0 && this.mapperType < this.mapperName.length) {
|
||||
return this.mapperName[this.mapperType];
|
||||
}
|
||||
return "Unknown Mapper, " + this.mapperType;
|
||||
},
|
||||
|
||||
mapperSupported: function() {
|
||||
return typeof Mappers[this.mapperType] !== "undefined";
|
||||
},
|
||||
|
||||
createMapper: function() {
|
||||
if (this.mapperSupported()) {
|
||||
return new Mappers[this.mapperType](this.nes);
|
||||
} else {
|
||||
throw new Error(
|
||||
"This ROM uses a mapper not supported by JSNES: " +
|
||||
this.getMapperName() +
|
||||
"(" +
|
||||
this.mapperType +
|
||||
")"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ROM;
|
198
fc2/source/tile.js
Normal file
@ -0,0 +1,198 @@
|
||||
var Tile = function() {
|
||||
// Tile data:
|
||||
this.pix = new Array(64);
|
||||
|
||||
this.fbIndex = null;
|
||||
this.tIndex = null;
|
||||
this.x = null;
|
||||
this.y = null;
|
||||
this.w = null;
|
||||
this.h = null;
|
||||
this.incX = null;
|
||||
this.incY = null;
|
||||
this.palIndex = null;
|
||||
this.tpri = null;
|
||||
this.c = null;
|
||||
this.initialized = false;
|
||||
this.opaque = new Array(8);
|
||||
};
|
||||
|
||||
Tile.prototype = {
|
||||
setBuffer: function(scanline) {
|
||||
for (this.y = 0; this.y < 8; this.y++) {
|
||||
this.setScanline(this.y, scanline[this.y], scanline[this.y + 8]);
|
||||
}
|
||||
},
|
||||
|
||||
setScanline: function(sline, b1, b2) {
|
||||
this.initialized = true;
|
||||
this.tIndex = sline << 3;
|
||||
for (this.x = 0; this.x < 8; this.x++) {
|
||||
this.pix[this.tIndex + this.x] =
|
||||
((b1 >> (7 - this.x)) & 1) + (((b2 >> (7 - this.x)) & 1) << 1);
|
||||
if (this.pix[this.tIndex + this.x] === 0) {
|
||||
this.opaque[sline] = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function(
|
||||
buffer,
|
||||
srcx1,
|
||||
srcy1,
|
||||
srcx2,
|
||||
srcy2,
|
||||
dx,
|
||||
dy,
|
||||
palAdd,
|
||||
palette,
|
||||
flipHorizontal,
|
||||
flipVertical,
|
||||
pri,
|
||||
priTable
|
||||
) {
|
||||
if (dx < -7 || dx >= 256 || dy < -7 || dy >= 240) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.w = srcx2 - srcx1;
|
||||
this.h = srcy2 - srcy1;
|
||||
|
||||
if (dx < 0) {
|
||||
srcx1 -= dx;
|
||||
}
|
||||
if (dx + srcx2 >= 256) {
|
||||
srcx2 = 256 - dx;
|
||||
}
|
||||
|
||||
if (dy < 0) {
|
||||
srcy1 -= dy;
|
||||
}
|
||||
if (dy + srcy2 >= 240) {
|
||||
srcy2 = 240 - dy;
|
||||
}
|
||||
|
||||
if (!flipHorizontal && !flipVertical) {
|
||||
this.fbIndex = (dy << 8) + dx;
|
||||
this.tIndex = 0;
|
||||
for (this.y = 0; this.y < 8; this.y++) {
|
||||
for (this.x = 0; this.x < 8; this.x++) {
|
||||
if (
|
||||
this.x >= srcx1 &&
|
||||
this.x < srcx2 &&
|
||||
this.y >= srcy1 &&
|
||||
this.y < srcy2
|
||||
) {
|
||||
this.palIndex = this.pix[this.tIndex];
|
||||
this.tpri = priTable[this.fbIndex];
|
||||
if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
|
||||
//console.log("Rendering upright tile to buffer");
|
||||
buffer[this.fbIndex] = palette[this.palIndex + palAdd];
|
||||
this.tpri = (this.tpri & 0xf00) | pri;
|
||||
priTable[this.fbIndex] = this.tpri;
|
||||
}
|
||||
}
|
||||
this.fbIndex++;
|
||||
this.tIndex++;
|
||||
}
|
||||
this.fbIndex -= 8;
|
||||
this.fbIndex += 256;
|
||||
}
|
||||
} else if (flipHorizontal && !flipVertical) {
|
||||
this.fbIndex = (dy << 8) + dx;
|
||||
this.tIndex = 7;
|
||||
for (this.y = 0; this.y < 8; this.y++) {
|
||||
for (this.x = 0; this.x < 8; this.x++) {
|
||||
if (
|
||||
this.x >= srcx1 &&
|
||||
this.x < srcx2 &&
|
||||
this.y >= srcy1 &&
|
||||
this.y < srcy2
|
||||
) {
|
||||
this.palIndex = this.pix[this.tIndex];
|
||||
this.tpri = priTable[this.fbIndex];
|
||||
if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
|
||||
buffer[this.fbIndex] = palette[this.palIndex + palAdd];
|
||||
this.tpri = (this.tpri & 0xf00) | pri;
|
||||
priTable[this.fbIndex] = this.tpri;
|
||||
}
|
||||
}
|
||||
this.fbIndex++;
|
||||
this.tIndex--;
|
||||
}
|
||||
this.fbIndex -= 8;
|
||||
this.fbIndex += 256;
|
||||
this.tIndex += 16;
|
||||
}
|
||||
} else if (flipVertical && !flipHorizontal) {
|
||||
this.fbIndex = (dy << 8) + dx;
|
||||
this.tIndex = 56;
|
||||
for (this.y = 0; this.y < 8; this.y++) {
|
||||
for (this.x = 0; this.x < 8; this.x++) {
|
||||
if (
|
||||
this.x >= srcx1 &&
|
||||
this.x < srcx2 &&
|
||||
this.y >= srcy1 &&
|
||||
this.y < srcy2
|
||||
) {
|
||||
this.palIndex = this.pix[this.tIndex];
|
||||
this.tpri = priTable[this.fbIndex];
|
||||
if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
|
||||
buffer[this.fbIndex] = palette[this.palIndex + palAdd];
|
||||
this.tpri = (this.tpri & 0xf00) | pri;
|
||||
priTable[this.fbIndex] = this.tpri;
|
||||
}
|
||||
}
|
||||
this.fbIndex++;
|
||||
this.tIndex++;
|
||||
}
|
||||
this.fbIndex -= 8;
|
||||
this.fbIndex += 256;
|
||||
this.tIndex -= 16;
|
||||
}
|
||||
} else {
|
||||
this.fbIndex = (dy << 8) + dx;
|
||||
this.tIndex = 63;
|
||||
for (this.y = 0; this.y < 8; this.y++) {
|
||||
for (this.x = 0; this.x < 8; this.x++) {
|
||||
if (
|
||||
this.x >= srcx1 &&
|
||||
this.x < srcx2 &&
|
||||
this.y >= srcy1 &&
|
||||
this.y < srcy2
|
||||
) {
|
||||
this.palIndex = this.pix[this.tIndex];
|
||||
this.tpri = priTable[this.fbIndex];
|
||||
if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
|
||||
buffer[this.fbIndex] = palette[this.palIndex + palAdd];
|
||||
this.tpri = (this.tpri & 0xf00) | pri;
|
||||
priTable[this.fbIndex] = this.tpri;
|
||||
}
|
||||
}
|
||||
this.fbIndex++;
|
||||
this.tIndex--;
|
||||
}
|
||||
this.fbIndex -= 8;
|
||||
this.fbIndex += 256;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isTransparent: function(x, y) {
|
||||
return this.pix[(y << 3) + x] === 0;
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return {
|
||||
opaque: this.opaque,
|
||||
pix: this.pix
|
||||
};
|
||||
},
|
||||
|
||||
fromJSON: function(s) {
|
||||
this.opaque = s.opaque;
|
||||
this.pix = s.pix;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Tile;
|
25
fc2/source/utils.js
Normal file
@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
copyArrayElements: function(src, srcPos, dest, destPos, length) {
|
||||
for (var i = 0; i < length; ++i) {
|
||||
dest[destPos + i] = src[srcPos + i];
|
||||
}
|
||||
},
|
||||
|
||||
copyArray: function(src) {
|
||||
return src.slice(0);
|
||||
},
|
||||
|
||||
fromJSON: function(obj, state) {
|
||||
for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {
|
||||
obj[obj.JSON_PROPERTIES[i]] = state[obj.JSON_PROPERTIES[i]];
|
||||
}
|
||||
},
|
||||
|
||||
toJSON: function(obj) {
|
||||
var state = {};
|
||||
for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {
|
||||
state[obj.JSON_PROPERTIES[i]] = obj[obj.JSON_PROPERTIES[i]];
|
||||
}
|
||||
return state;
|
||||
}
|
||||
};
|
135
gulpfile.js
@ -1,130 +1,7 @@
|
||||
var gulp = require('gulp'),
|
||||
concat = require('gulp-concat'),
|
||||
// uglify = require('gulp-uglify'),
|
||||
uglify = require('gulp-uglify-es').default,
|
||||
rename = require('gulp-rename'),
|
||||
jshint = require('gulp-jshint'),
|
||||
stripDebug = require("gulp-strip-debug"),//移除console语句
|
||||
cleanCSS = require('gulp-clean-css'),
|
||||
gutil = require('gulp-util');
|
||||
const javascriptObfuscator = require('gulp-javascript-obfuscator');
|
||||
const obfuscatorOptions = {
|
||||
compact:true,
|
||||
sourceMap: false,
|
||||
debugProtection: true,
|
||||
disableConsoleOutput: true,
|
||||
domainLock: ['.kingsome.cn', '.zhl.com']
|
||||
};
|
||||
//检查js的语法错误
|
||||
gulp.task('jslint', function () {
|
||||
return gulp.src('fc/js/*.js')
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'))
|
||||
.pipe(notify({message: 'finished jslint'}));
|
||||
});
|
||||
//发布所有html
|
||||
gulp.task('disthtml', function(){
|
||||
return gulp.src(['**/*.html', '!node_modules/**/*', '!dist/**/*'])
|
||||
.pipe(gulp.dest('dist'));
|
||||
})
|
||||
//处理fc的css
|
||||
gulp.task('distfccss', function() {
|
||||
return gulp.src(['fc/css/**/*.css'])
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('dist/fc/css'));
|
||||
})
|
||||
//处理fc的图片资源
|
||||
gulp.task('distfcstatic', function() {
|
||||
return gulp.src(['fc/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/fc/resources'));
|
||||
})
|
||||
gulp.task('distfclib', function() {
|
||||
return gulp.src(['fc/lib/**/*'])
|
||||
.pipe(gulp.dest('dist/fc/lib'));
|
||||
})
|
||||
gulp.task('distfcjs', function() {
|
||||
return gulp.src(['fc/js/**/*.js'])
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/fc/js'));
|
||||
});
|
||||
gulp.task('distfc', ['distfcjs', 'distfccss', 'distfcstatic', 'distfclib'])
|
||||
let gulpRequireTasks = require('gulp-require-tasks');
|
||||
let gulp = require('gulp')
|
||||
|
||||
gulp.task('distgbajs', function() {
|
||||
return gulp.src(['gba/dist/*.js'])
|
||||
// .pipe(concat('main.js'))
|
||||
// .pipe(rename({suffix: '.min'}))
|
||||
//https://github.com/mishoo/UglifyJS2
|
||||
// .pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba/dist'));
|
||||
});
|
||||
gulp.task('distgbacss', function() {
|
||||
return gulp.src(['gba/dist/**/*.css'])
|
||||
.pipe(gulp.dest('dist/gba/dist'));
|
||||
})
|
||||
gulp.task('distgbastatic', function() {
|
||||
return gulp.src(['gba/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/gba/resources'));
|
||||
})
|
||||
gulp.task('distgbajsext', function() {
|
||||
return gulp.src(['gba/js/**/*'])
|
||||
.pipe(gulp.dest('dist/gba/js'));
|
||||
})
|
||||
gulp.task('distgba', ['distgbajs', 'distgbacss', 'distgbastatic', 'distgbajsext'])
|
||||
|
||||
gulp.task('distgba2js', function() {
|
||||
return gulp.src(['gba2/dist/*.js'])
|
||||
// .pipe(concat('main.js'))
|
||||
// .pipe(rename({suffix: '.min'}))
|
||||
//https://github.com/mishoo/UglifyJS2
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba2/dist'));
|
||||
});
|
||||
gulp.task('distgba2css', function() {
|
||||
return gulp.src(['gba2/dist/**/*.css'])
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('dist/gba2/dist'));
|
||||
})
|
||||
gulp.task('distgba2static', function() {
|
||||
return gulp.src(['gba2/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/resources'));
|
||||
})
|
||||
gulp.task('distgba2bios', function() {
|
||||
return gulp.src(['gba2/Binaries/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/Binaries'));
|
||||
})
|
||||
gulp.task('distgba2jscore', function() {
|
||||
return gulp.src(['gba2/IodineGBA/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/IodineGBA'));
|
||||
})
|
||||
gulp.task('distgba2jsuser', function() {
|
||||
return gulp.src(['gba2/user_scripts/**/*'])
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba2/user_scripts'));
|
||||
})
|
||||
gulp.task('distgba2', ['distgba2js', 'distgba2css', 'distgba2static', 'distgba2bios', 'distgba2jscore', 'distgba2jsuser'])
|
||||
gulp.task('assets', function() {
|
||||
return gulp.src(['assets/**/*'])
|
||||
.pipe(gulp.dest('dist/assets'));
|
||||
})
|
||||
// movie
|
||||
gulp.task('distmoviejs', function() {
|
||||
return gulp.src(['movie/js/*.js'])
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/movie/js'));
|
||||
});
|
||||
gulp.task('distmoviecss', function() {
|
||||
return gulp.src(['movie/css/**/*.css'])
|
||||
.pipe(gulp.dest('dist/movie/css'));
|
||||
})
|
||||
gulp.task('distmoviestatic', function() {
|
||||
return gulp.src(['movie/img/**/*'])
|
||||
.pipe(gulp.dest('dist/movie/img'));
|
||||
})
|
||||
gulp.task('distmovie', ['distmoviejs', 'distmoviecss', 'distmoviestatic'])
|
||||
|
||||
gulp.task('default', ['disthtml', 'distfc', 'distgba', 'distgba2', 'distmovie', 'assets']);
|
||||
// gulp.task('default', ['disthtml', 'distfc', 'assets']);
|
||||
gulpRequireTasks({
|
||||
path: __dirname + '/tasks',
|
||||
gulp: gulp
|
||||
});
|
||||
|
40
package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "emulator",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "",
|
||||
"devDependencies": {
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-clean-css": "^4.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-javascript-obfuscator": "^1.1.5",
|
||||
"gulp-jshint": "^2.1.0",
|
||||
"gulp-minify-css": "^1.2.4",
|
||||
"gulp-module-wrapper": "^0.3.8",
|
||||
"gulp-notify": "^3.2.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-require-tasks": "^1.2.1",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-strip-debug": "^3.0.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"gulp-uglify-es": "^1.0.4",
|
||||
"gulp-util": "^3.0.8",
|
||||
"jshint": "^2.10.1",
|
||||
"path": "^0.12.7",
|
||||
"gulp-browserify": "^0.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build_fc_lib": "webpack --config tasks/fc2.webpack.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.kingsome.cn:weapp/emulator.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
134
tasks/dist.js
Normal file
@ -0,0 +1,134 @@
|
||||
var gulp = require('gulp'),
|
||||
concat = require('gulp-concat'),
|
||||
// uglify = require('gulp-uglify'),
|
||||
uglify = require('gulp-uglify-es').default,
|
||||
rename = require('gulp-rename'),
|
||||
jshint = require('gulp-jshint'),
|
||||
stripDebug = require("gulp-strip-debug"),//移除console语句
|
||||
cleanCSS = require('gulp-clean-css'),
|
||||
gutil = require('gulp-util');
|
||||
const javascriptObfuscator = require('gulp-javascript-obfuscator');
|
||||
const obfuscatorOptions = {
|
||||
compact:true,
|
||||
sourceMap: false,
|
||||
debugProtection: true,
|
||||
disableConsoleOutput: true,
|
||||
domainLock: ['.kingsome.cn', '.zhl.com']
|
||||
};
|
||||
//检查js的语法错误
|
||||
gulp.task('jslint', function () {
|
||||
return gulp.src('fc/js/*.js')
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'))
|
||||
.pipe(notify({message: 'finished jslint'}));
|
||||
});
|
||||
//发布所有html
|
||||
gulp.task('disthtml', function(){
|
||||
return gulp.src(['**/*.html', '!node_modules/**/*', '!dist/**/*'])
|
||||
.pipe(gulp.dest('dist'));
|
||||
})
|
||||
//处理fc的css
|
||||
gulp.task('distfccss', function() {
|
||||
return gulp.src(['fc/css/**/*.css'])
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('dist/fc/css'));
|
||||
})
|
||||
//处理fc的图片资源
|
||||
gulp.task('distfcstatic', function() {
|
||||
return gulp.src(['fc/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/fc/resources'));
|
||||
})
|
||||
gulp.task('distfclib', function() {
|
||||
return gulp.src(['fc/lib/**/*'])
|
||||
.pipe(gulp.dest('dist/fc/lib'));
|
||||
})
|
||||
gulp.task('distfcjs', function() {
|
||||
return gulp.src(['fc/js/**/*.js'])
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/fc/js'));
|
||||
});
|
||||
gulp.task('distfc', ['distfcjs', 'distfccss', 'distfcstatic', 'distfclib'])
|
||||
|
||||
gulp.task('distgbajs', function() {
|
||||
return gulp.src(['gba/dist/*.js'])
|
||||
// .pipe(concat('main.js'))
|
||||
// .pipe(rename({suffix: '.min'}))
|
||||
//https://github.com/mishoo/UglifyJS2
|
||||
// .pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba/dist'));
|
||||
});
|
||||
gulp.task('distgbacss', function() {
|
||||
return gulp.src(['gba/dist/**/*.css'])
|
||||
.pipe(gulp.dest('dist/gba/dist'));
|
||||
})
|
||||
gulp.task('distgbastatic', function() {
|
||||
return gulp.src(['gba/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/gba/resources'));
|
||||
})
|
||||
gulp.task('distgbajsext', function() {
|
||||
return gulp.src(['gba/js/**/*'])
|
||||
.pipe(gulp.dest('dist/gba/js'));
|
||||
})
|
||||
gulp.task('distgba', ['distgbajs', 'distgbacss', 'distgbastatic', 'distgbajsext'])
|
||||
|
||||
gulp.task('distgba2js', function() {
|
||||
return gulp.src(['gba2/dist/*.js'])
|
||||
// .pipe(concat('main.js'))
|
||||
// .pipe(rename({suffix: '.min'}))
|
||||
//https://github.com/mishoo/UglifyJS2
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba2/dist'));
|
||||
});
|
||||
gulp.task('distgba2css', function() {
|
||||
return gulp.src(['gba2/dist/**/*.css'])
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('dist/gba2/dist'));
|
||||
})
|
||||
gulp.task('distgba2static', function() {
|
||||
return gulp.src(['gba2/resources/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/resources'));
|
||||
})
|
||||
gulp.task('distgba2bios', function() {
|
||||
return gulp.src(['gba2/Binaries/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/Binaries'));
|
||||
})
|
||||
gulp.task('distgba2jscore', function() {
|
||||
return gulp.src(['gba2/IodineGBA/**/*'])
|
||||
.pipe(gulp.dest('dist/gba2/IodineGBA'));
|
||||
})
|
||||
gulp.task('distgba2jsuser', function() {
|
||||
return gulp.src(['gba2/user_scripts/**/*'])
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/gba2/user_scripts'));
|
||||
})
|
||||
gulp.task('distgba2', ['distgba2js', 'distgba2css', 'distgba2static', 'distgba2bios', 'distgba2jscore', 'distgba2jsuser'])
|
||||
gulp.task('assets', function() {
|
||||
return gulp.src(['assets/**/*'])
|
||||
.pipe(gulp.dest('dist/assets'));
|
||||
})
|
||||
// movie
|
||||
gulp.task('distmoviejs', function() {
|
||||
return gulp.src(['movie/js/*.js'])
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/movie/js'));
|
||||
});
|
||||
gulp.task('distmoviecss', function() {
|
||||
return gulp.src(['movie/css/**/*.css'])
|
||||
.pipe(gulp.dest('dist/movie/css'));
|
||||
})
|
||||
gulp.task('distmoviestatic', function() {
|
||||
return gulp.src(['movie/img/**/*'])
|
||||
.pipe(gulp.dest('dist/movie/img'));
|
||||
})
|
||||
gulp.task('distmovie', ['distmoviejs', 'distmoviecss', 'distmoviestatic'])
|
||||
|
||||
gulp.task('default', ['disthtml', 'distfc', 'distgba', 'distgba2', 'distmovie', 'assets']);
|
||||
// gulp.task('default', ['disthtml', 'distfc', 'assets']);
|
||||
|
||||
module.exports = {
|
||||
dep: ['default'],
|
||||
};
|
54
tasks/fc2.js
Normal file
@ -0,0 +1,54 @@
|
||||
const gulp = require('gulp'),
|
||||
concat = require('gulp-concat'),
|
||||
// uglify = require('gulp-uglify'),
|
||||
uglify = require('gulp-uglify-es').default,
|
||||
rename = require('gulp-rename'),
|
||||
jshint = require('gulp-jshint'),
|
||||
stripDebug = require("gulp-strip-debug"),//移除console语句
|
||||
cleanCSS = require('gulp-clean-css'),
|
||||
gutil = require('gulp-util'),
|
||||
browserify = require('gulp-browserify'),
|
||||
javascriptObfuscator = require('gulp-javascript-obfuscator');
|
||||
|
||||
const obfuscatorOptions = {
|
||||
compact:true,
|
||||
sourceMap: false,
|
||||
debugProtection: false,
|
||||
disableConsoleOutput: false
|
||||
};
|
||||
|
||||
//处理fc的css
|
||||
gulp.task('distfccss', function() {
|
||||
return gulp.src(['fc2/css/**/*.css'])
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('dist/fc2/css'));
|
||||
})
|
||||
//处理fc的图片资源
|
||||
gulp.task('distfcstatic', function() {
|
||||
return gulp.src(['fc2/images/**/*'])
|
||||
.pipe(gulp.dest('dist/fc2/images'));
|
||||
})
|
||||
gulp.task('distfcjs', function() {
|
||||
return gulp.src(['fc2/js/**/*.js'])
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(stripDebug())
|
||||
.pipe(gulp.dest('dist/fc2/js'));
|
||||
});
|
||||
|
||||
gulp.task('distlib', function () {
|
||||
return gulp.src('fc2/source/index.js')
|
||||
.pipe(browserify({
|
||||
insertGlobals : false,
|
||||
debug: false,
|
||||
standalone: 'jsnes'
|
||||
}))
|
||||
.pipe(javascriptObfuscator(obfuscatorOptions))
|
||||
.pipe(rename('jsnes.min.js'))
|
||||
.pipe(gulp.dest('dist/fc2/js/'));
|
||||
});
|
||||
gulp.task('distfc', ['distfcjs', 'distfccss', 'distfcstatic', 'distlib'])
|
||||
|
||||
module.exports = {
|
||||
dep: ['distfc'],
|
||||
};
|
||||
|