mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-22 22:48:08 +03:00
Compare commits
570
Commits
win-2.1.0
...
1918350b19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1918350b19 | ||
|
|
9504ee7b65 | ||
|
|
f033a507d3 | ||
|
|
fbeb9a780e | ||
|
|
8c69f53073 | ||
|
|
cc96c85381 | ||
|
|
a2618319d9 | ||
|
|
30fd5768da | ||
|
|
15a769d83c | ||
|
|
253de6adc8 | ||
|
|
a0daa3261c | ||
|
|
98e9d8bcfb | ||
|
|
b7854aaec1 | ||
|
|
905eb34590 | ||
|
|
8f22566adb | ||
|
|
48ded43e2a | ||
|
|
571bec63a2 | ||
|
|
345c017c22 | ||
|
|
205004086b | ||
|
|
6820bcfcb8 | ||
|
|
5ce1494b59 | ||
|
|
dd45e566f6 | ||
|
|
1ccc7630c1 | ||
|
|
6456f59c37 | ||
|
|
f748e8f678 | ||
|
|
a9e23508b7 | ||
|
|
c3bd978f02 | ||
|
|
50a6b17c5e | ||
|
|
0d49619366 | ||
|
|
da3cc5d900 | ||
|
|
1785e93fcc | ||
|
|
a77e849f82 | ||
|
|
978dbdbc3b | ||
|
|
e3c168c5a6 | ||
|
|
9f8c2af842 | ||
|
|
5d8b1c5632 | ||
|
|
30188c3a4f | ||
|
|
a627a8ea3d | ||
|
|
0cf9c1fe8d | ||
|
|
0887c44292 | ||
|
|
27a0c89a73 | ||
|
|
5c827225ab | ||
|
|
bc37ba6256 | ||
|
|
ab4bc2d8d1 | ||
|
|
f26321c5e0 | ||
|
|
1d897c72d0 | ||
|
|
172c76b4e2 | ||
|
|
100584f896 | ||
|
|
975f618b53 | ||
|
|
0c8bab0c64 | ||
|
|
0192fd3d8e | ||
|
|
d69fc5c6a3 | ||
|
|
68a39843e2 | ||
|
|
e3d7b60785 | ||
|
|
2bb891ada8 | ||
|
|
f109fe11f5 | ||
|
|
73b3377b77 | ||
|
|
9400572502 | ||
|
|
a1e063c4b2 | ||
|
|
602c55a5e5 | ||
|
|
9624192a41 | ||
|
|
5c7436e2b1 | ||
|
|
6c9844697f | ||
|
|
210c0a13e4 | ||
|
|
3ebb1faece | ||
|
|
286f150510 | ||
|
|
a194cf10a6 | ||
|
|
9587d350ec | ||
|
|
02f62d51bb | ||
|
|
54b98ec3e4 | ||
|
|
8cde827ee6 | ||
|
|
a5d67b067c | ||
|
|
5cb70110b9 | ||
|
|
e3dcadd5dd | ||
|
|
ff31d03153 | ||
|
|
63886d7d00 | ||
|
|
c119c2f969 | ||
|
|
fb63d42ec1 | ||
|
|
6f631d073d | ||
|
|
49dca57fbd | ||
|
|
c8987da470 | ||
|
|
0dfb3eb378 | ||
|
|
269132fede | ||
|
|
651fc21a19 | ||
|
|
03f86bc4d7 | ||
|
|
4ffab8e4e9 | ||
|
|
1593452146 | ||
|
|
50d05e5b56 | ||
|
|
c4026f9eb1 | ||
|
|
3318bebf6c | ||
|
|
d4ad329583 | ||
|
|
2e828710ca | ||
|
|
fad4236458 | ||
|
|
77d62c7c13 | ||
|
|
a726d577ef | ||
|
|
4a5cd4a1c1 | ||
|
|
022d6af4f1 | ||
|
|
df816e3708 | ||
|
|
2c9079c875 | ||
|
|
54b8d6c993 | ||
|
|
f2fe6b9724 | ||
|
|
38dc9a7b0b | ||
|
|
d2ac3889b5 | ||
|
|
0342141358 | ||
|
|
8806bc9545 | ||
|
|
5e62405f17 | ||
|
|
714c7d44b5 | ||
|
|
0f6d4e2343 | ||
|
|
f1536a801a | ||
|
|
ab372b7873 | ||
|
|
2c02f40dc4 | ||
|
|
a395366cf8 | ||
|
|
e416fc20d3 | ||
|
|
7d03fc6757 | ||
|
|
e3b8c90611 | ||
|
|
361ca89016 | ||
|
|
5bd4622031 | ||
|
|
c7c0f69ec4 | ||
|
|
f38a3eda5c | ||
|
|
dc17b78365 | ||
|
|
b9f68eedd3 | ||
|
|
adf362ea57 | ||
|
|
b5bf439b8e | ||
|
|
9a01d28075 | ||
|
|
945dbd7722 | ||
|
|
1c54487041 | ||
|
|
92389289da | ||
|
|
4de03cc77f | ||
|
|
5a81ac6fc8 | ||
|
|
d7bac22059 | ||
|
|
8d4424061d | ||
|
|
6c692e44e5 | ||
|
|
ec071d4e8a | ||
|
|
904e10daa7 | ||
|
|
65b6da3f2a | ||
|
|
3f495b72c1 | ||
|
|
90670477c6 | ||
|
|
566ebb503f | ||
|
|
6feab51a5d | ||
|
|
15d0b44ab9 | ||
|
|
a0f20caa77 | ||
|
|
39712ee46e | ||
|
|
dcdbe7bb76 | ||
|
|
01a4255b83 | ||
|
|
d978a24a5e | ||
|
|
ed196f72b9 | ||
|
|
c9fd1dcfd0 | ||
|
|
c1018d9e0f | ||
|
|
cb8a48cdec | ||
|
|
6bf6a5c86a | ||
|
|
790aa07130 | ||
|
|
fa74a4744c | ||
|
|
fb68b03a4f | ||
|
|
75228cc4c8 | ||
|
|
8c13361164 | ||
|
|
d05dc83e67 | ||
|
|
c9f21c4547 | ||
|
|
f053f25397 | ||
|
|
f673708a3f | ||
|
|
aab63adac2 | ||
|
|
6f09a999d2 | ||
|
|
2ed44dab6d | ||
|
|
a8f46453d8 | ||
|
|
baa4a3158f | ||
|
|
8328b93227 | ||
|
|
6665416683 | ||
|
|
cf5e899632 | ||
|
|
51954c6bd4 | ||
|
|
f282c02376 | ||
|
|
e2c75fbd9d | ||
|
|
b28e3ef2fd | ||
|
|
215de5511e | ||
|
|
f669804573 | ||
|
|
9c6423bf99 | ||
|
|
4f3b73c1ac | ||
|
|
8ee53accf9 | ||
|
|
6b646b3857 | ||
|
|
dec1649882 | ||
|
|
6b9c3475a0 | ||
|
|
1335a12065 | ||
|
|
93bd8ca737 | ||
|
|
5bae0c16c5 | ||
|
|
d551cdbb70 | ||
|
|
e711d5786e | ||
|
|
64abb3c582 | ||
|
|
bf26f1b0c0 | ||
|
|
f4f1a99372 | ||
|
|
53f0a5ce2a | ||
|
|
c621a5dddb | ||
|
|
42faf38a47 | ||
|
|
c50a563e80 | ||
|
|
996f63f55f | ||
|
|
fb77b229ea | ||
|
|
886d55057a | ||
|
|
7f645d6bd1 | ||
|
|
f8d59e5fc0 | ||
|
|
78ebbf40f4 | ||
|
|
dccd75113b | ||
|
|
d063655b25 | ||
|
|
5e0844e9ea | ||
|
|
386c3c0fcd | ||
|
|
2857c13e15 | ||
|
|
d14b42c146 | ||
|
|
29988d0d6c | ||
|
|
bc5d72b6fe | ||
|
|
03f9b36ff0 | ||
|
|
afb9f96a1a | ||
|
|
28e685818c | ||
|
|
51eca5c8c0 | ||
|
|
1699fbda6c | ||
|
|
abbe8b9be1 | ||
|
|
10735b8be6 | ||
|
|
e9d8a9d1a6 | ||
|
|
780d30c01f | ||
|
|
d6adb3241c | ||
|
|
3594fc19dc | ||
|
|
883f538709 | ||
|
|
c280fc719b | ||
|
|
7da9e64ab6 | ||
|
|
bba602ab04 | ||
|
|
78dc226d13 | ||
|
|
a2792d502e | ||
|
|
e800a23e1d | ||
|
|
e731b4ec95 | ||
|
|
dde697d3b4 | ||
|
|
c4d4f85ae4 | ||
|
|
84e3666a66 | ||
|
|
c2f11ecfbe | ||
|
|
85d69aa5f0 | ||
|
|
7beb558140 | ||
|
|
49126c506d | ||
|
|
4c57c98e67 | ||
|
|
e464d92164 | ||
|
|
b30fd71e4b | ||
|
|
99ad91306b | ||
|
|
ed96f26cea | ||
|
|
e6f71190ed | ||
|
|
8219feee6d | ||
|
|
bc8a85e046 | ||
|
|
67f62f02e6 | ||
|
|
471c65973b | ||
|
|
5ff7149dc1 | ||
|
|
80988e52cf | ||
|
|
ade76adda6 | ||
|
|
53d0e8ffef | ||
|
|
b2e14bd19a | ||
|
|
9c73a34c35 | ||
|
|
36e1dc152b | ||
|
|
f786db5a50 | ||
|
|
c515b21afb | ||
|
|
d3db3fa83e | ||
|
|
86068b0ab9 | ||
|
|
de4f3ebb0d | ||
|
|
187a3ced35 | ||
|
|
b7703b6270 | ||
|
|
b7d0efbe55 | ||
|
|
02fe55eb93 | ||
|
|
058789b435 | ||
|
|
c5dcb20ac3 | ||
|
|
cb08f7ca76 | ||
|
|
7a7d94e60f | ||
|
|
b7681982a6 | ||
|
|
fefaf6c343 | ||
|
|
42e994dfba | ||
|
|
a6aa50b5cf | ||
|
|
c4e8205d6c | ||
|
|
dd09f54329 | ||
|
|
435ceb31d3 | ||
|
|
e40de66821 | ||
|
|
73c79b7a2e | ||
|
|
a825ee80b3 | ||
|
|
eeeab6e054 | ||
|
|
875b233f32 | ||
|
|
bcaed3a687 | ||
|
|
894e8e26a6 | ||
|
|
7206b0a4f7 | ||
|
|
343f3bb7c5 | ||
|
|
d3650ce293 | ||
|
|
386cfeb60c | ||
|
|
9cd66c83ef | ||
|
|
065e16a082 | ||
|
|
3699aa5c2d | ||
|
|
20914970c9 | ||
|
|
3f2f555aa6 | ||
|
|
095da0bb86 | ||
|
|
7b8acd6e05 | ||
|
|
1b13e331e4 | ||
|
|
5621ab4d86 | ||
|
|
79805d9ea2 | ||
|
|
f427d50fa3 | ||
|
|
7c7768fba4 | ||
|
|
2b6267aeb9 | ||
|
|
16e7d3baf1 | ||
|
|
2d8d95ac6f | ||
|
|
d5899ff55a | ||
|
|
04037682ee | ||
|
|
cdb16f73a3 | ||
|
|
2895e338c0 | ||
|
|
1a2268e94b | ||
|
|
07643c82a4 | ||
|
|
a16156f8e4 | ||
|
|
ea5cef91fd | ||
|
|
48a41b9f82 | ||
|
|
c11c80d95d | ||
|
|
d7082a9d80 | ||
|
|
083cf885de | ||
|
|
7b1fef4ad7 | ||
|
|
f039baf061 | ||
|
|
22c6b8e963 | ||
|
|
576afecbfc | ||
|
|
4e28d92a2d | ||
|
|
22192c245f | ||
|
|
409fa83406 | ||
|
|
894b0592f4 | ||
|
|
370100a6f2 | ||
|
|
18cfbc90f2 | ||
|
|
cd51dc7f02 | ||
|
|
e2638ed840 | ||
|
|
6607815df3 | ||
|
|
77ed85aff7 | ||
|
|
3b1a311f32 | ||
|
|
1b3c2190b8 | ||
|
|
52e2d6095a | ||
|
|
47e2fcaec0 | ||
|
|
183ceb614a | ||
|
|
185e62010b | ||
|
|
6f647701dd | ||
|
|
a9c79b2e31 | ||
|
|
ba52fbf5e9 | ||
|
|
85d40701c4 | ||
|
|
7b535b371a | ||
|
|
c0dbae8202 | ||
|
|
ca03450b87 | ||
|
|
f795a500cf | ||
|
|
3314f00bf8 | ||
|
|
a2d79f045c | ||
|
|
f247495578 | ||
|
|
f6583330de | ||
|
|
4b15f45897 | ||
|
|
7bc433731f | ||
|
|
4c21570eb1 | ||
|
|
a3e7feffc2 | ||
|
|
a4c1dd1acf | ||
|
|
09086ccc85 | ||
|
|
53e39b7c11 | ||
|
|
f0ccfee40f | ||
|
|
10cdec98e6 | ||
|
|
c61afb4308 | ||
|
|
f6cf881e61 | ||
|
|
a8277e71a7 | ||
|
|
3482fb6440 | ||
|
|
3c81c8fec2 | ||
|
|
e090485b37 | ||
|
|
d7deaec972 | ||
|
|
30467371f4 | ||
|
|
e9a30f7493 | ||
|
|
d9e42dd4bb | ||
|
|
0e06cc1e22 | ||
|
|
89423c94f1 | ||
|
|
c694d2282c | ||
|
|
39e94afc44 | ||
|
|
e37a491908 | ||
|
|
43dff9a9e4 | ||
|
|
e6b3d6094d | ||
|
|
61ad4282e6 | ||
|
|
e4628d8bbb | ||
|
|
21bd4279a1 | ||
|
|
b6c33fd761 | ||
|
|
1b9d8c48f1 | ||
|
|
55ef307e07 | ||
|
|
894cd51e01 | ||
|
|
880244020e | ||
|
|
9c2f423399 | ||
|
|
654a489f49 | ||
|
|
912af592ae | ||
|
|
6d70439dff | ||
|
|
aec06f775a | ||
|
|
446ffe9466 | ||
|
|
02cad74568 | ||
|
|
33e1ff3b4a | ||
|
|
11f8f00072 | ||
|
|
b59d683fef | ||
|
|
b9f4c7c03a | ||
|
|
2d2c6aa373 | ||
|
|
850af6d140 | ||
|
|
da4cf017bf | ||
|
|
e88dfcdaa1 | ||
|
|
70a940b216 | ||
|
|
db2bb03fdd | ||
|
|
5ef949ae14 | ||
|
|
043028cf7d | ||
|
|
eace5d1dbd | ||
|
|
878bea69d6 | ||
|
|
ae8becc4a3 | ||
|
|
1d863cfb2e | ||
|
|
7bb2bee693 | ||
|
|
47e7f5542e | ||
|
|
8087d11b2b | ||
|
|
4f1521ac90 | ||
|
|
99632397da | ||
|
|
59283815d8 | ||
|
|
a32c901eb8 | ||
|
|
03b84b8ba7 | ||
|
|
720da4e42c | ||
|
|
c4dc799c94 | ||
|
|
ce271ae5eb | ||
|
|
526e5f8709 | ||
|
|
c967e63be9 | ||
|
|
d4a32aee17 | ||
|
|
1191723612 | ||
|
|
addf2e3728 | ||
|
|
4785c8c77c | ||
|
|
b1ac106057 | ||
|
|
acdc40ec60 | ||
|
|
d9f635a6d0 | ||
|
|
e8357c4e6f | ||
|
|
8e44a87069 | ||
|
|
d1befbd355 | ||
|
|
ec0bae5490 | ||
|
|
f5b3b1a936 | ||
|
|
1b546e3bab | ||
|
|
d454899048 | ||
|
|
7aa704b3f4 | ||
|
|
50d7d7ee33 | ||
|
|
586a9c855e | ||
|
|
9600e34229 | ||
|
|
001c97ea00 | ||
|
|
b100dbe26c | ||
|
|
55e38f9db2 | ||
|
|
ddf6fba393 | ||
|
|
83fa5db82c | ||
|
|
1807cf0c26 | ||
|
|
5906079d3f | ||
|
|
7fba19fe18 | ||
|
|
8de86e276d | ||
|
|
64a5f47258 | ||
|
|
29a139c8a1 | ||
|
|
b53c82e072 | ||
|
|
307bb2623b | ||
|
|
d1712ab7b4 | ||
|
|
7c24ba276d | ||
|
|
c8f9880efb | ||
|
|
d508676c70 | ||
|
|
92506d6a94 | ||
|
|
0a37920c95 | ||
|
|
fa44889047 | ||
|
|
cee18a572e | ||
|
|
652d284628 | ||
|
|
d2661e97f1 | ||
|
|
0e9afe7c0a | ||
|
|
fbe8af5f7b | ||
|
|
0c1f8f8450 | ||
|
|
078525e2c2 | ||
|
|
d4a4022894 | ||
|
|
dc89c92139 | ||
|
|
4f351fc767 | ||
|
|
02ef4a1771 | ||
|
|
4a20d74f1a | ||
|
|
c9663c5cfc | ||
|
|
5c7e92391c | ||
|
|
0a15e6490f | ||
|
|
fe294bd34b | ||
|
|
e26779a189 | ||
|
|
f9b5fec03d | ||
|
|
d63e3503e7 | ||
|
|
3dd38959a3 | ||
|
|
f56ec88cda | ||
|
|
9706f516f9 | ||
|
|
b15690d9b7 | ||
|
|
26db7f46f5 | ||
|
|
1cf0dbc7e2 | ||
|
|
3f36eeee4c | ||
|
|
ff62533faf | ||
|
|
f60b21bc19 | ||
|
|
8fac291e84 | ||
|
|
782a9e5925 | ||
|
|
c8f1ef2b9e | ||
|
|
6184e93965 | ||
|
|
3377441caf | ||
|
|
d243358b48 | ||
|
|
9e82445ace | ||
|
|
a6109b6210 | ||
|
|
901a3bb28c | ||
|
|
f1b80d7086 | ||
|
|
f1fb50841c | ||
|
|
f97259bc30 | ||
|
|
3eccaa6165 | ||
|
|
42b9d43a70 | ||
|
|
a7800e652c | ||
|
|
1c2e395e88 | ||
|
|
c67198cc0e | ||
|
|
fc80275944 | ||
|
|
76db37756b | ||
|
|
4c022cf505 | ||
|
|
1c3dccb349 | ||
|
|
63c456329f | ||
|
|
141b754340 | ||
|
|
45612feb79 | ||
|
|
605de284e9 | ||
|
|
0b71a84bb0 | ||
|
|
933cf5b6a3 | ||
|
|
c15e7233b4 | ||
|
|
bad6b90e23 | ||
|
|
83989f1a0e | ||
|
|
cc9aa1e7c0 | ||
|
|
d1d943d2a5 | ||
|
|
6c59122c59 | ||
|
|
a6cbf243c7 | ||
|
|
a89ed4ba29 | ||
|
|
135cd6b8c4 | ||
|
|
b6a69deab7 | ||
|
|
eea1f9c93a | ||
|
|
47d23d4ee7 | ||
|
|
10b414fb4e | ||
|
|
49e707e40e | ||
|
|
9655a9106d | ||
|
|
d35a24f142 | ||
|
|
d945dfe77d | ||
|
|
a224d89233 | ||
|
|
debabaf686 | ||
|
|
bc97e4c826 | ||
|
|
c4d74d659a | ||
|
|
4daaa5ede5 | ||
|
|
9ea1a81cc1 | ||
|
|
0a1d0a5a76 | ||
|
|
5fefd74d4a | ||
|
|
d03568c3ff | ||
|
|
8dc49fee71 | ||
|
|
1b907e904f | ||
|
|
82963e8293 | ||
|
|
3ccf59a94e | ||
|
|
2f9ce8cec8 | ||
|
|
3ff022c73e | ||
|
|
9ad1059ced | ||
|
|
2d026c6122 | ||
|
|
b68c217554 | ||
|
|
7e09c14660 | ||
|
|
2db403a692 | ||
|
|
850fa96933 | ||
|
|
e1e1e5cacc | ||
|
|
d7dd98a8ca | ||
|
|
f11dbc7400 | ||
|
|
3b52efb752 | ||
|
|
93f01efdb5 | ||
|
|
3012412a5d | ||
|
|
91fa4284c4 | ||
|
|
93d29b5f87 | ||
|
|
b6cdba145f | ||
|
|
38a40e82f3 | ||
|
|
d29a764de2 | ||
|
|
0d74454355 | ||
|
|
1a248ea897 | ||
|
|
556b9687ca | ||
|
|
8be63b7f11 | ||
|
|
8c963f4dcf | ||
|
|
87316495e8 | ||
|
|
45b50fe78a | ||
|
|
c31da0fe10 | ||
|
|
0d376a3d35 | ||
|
|
ff9710426f | ||
|
|
aa537687c4 | ||
|
|
ae30b73333 | ||
|
|
28ac087bca | ||
|
|
fc3d66195f | ||
|
|
f7ca54104b | ||
|
|
d8513d78e4 | ||
|
|
d0ad06e7c5 | ||
|
|
cae31a72d1 | ||
|
|
1405c8693b | ||
|
|
5c5a71237c |
@@ -10,6 +10,7 @@ assignees: ''
|
||||
### Environment
|
||||
- xsystem35 version:
|
||||
- OS:
|
||||
- Game title:
|
||||
|
||||
### Steps to reproduce the behavior
|
||||
1.
|
||||
@@ -22,3 +23,4 @@ assignees: ''
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
### Additional context
|
||||
<!-- Note: Please do not post copyrighted game files. -->
|
||||
|
||||
@@ -6,24 +6,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Deps
|
||||
run: sudo apt install ninja-build
|
||||
|
||||
# Workaround for https://github.com/actions/virtual-environments/issues/3078
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.9
|
||||
with:
|
||||
cmake-version: '3.19.x'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd android
|
||||
ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk-bundle ./gradlew assembleRelease
|
||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks
|
||||
JAVA_HOME=$JAVA_HOME_17_X64 ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk-bundle ./gradlew assembleRelease
|
||||
env:
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xsystem35-release-unsigned-apk
|
||||
path: android/app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
path: android/app/build/outputs/apk/release/app-release.apk
|
||||
archive: false
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Emscripten Build
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
EM_VERSION: 2.0.25
|
||||
EM_VERSION: latest
|
||||
EM_CACHE_FOLDER: 'emsdk-cache'
|
||||
|
||||
jobs:
|
||||
@@ -10,32 +10,35 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup cache
|
||||
id: cache-system-libraries
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.EM_CACHE_FOLDER}}
|
||||
key: ${{env.EM_VERSION}}-${{ runner.os }}
|
||||
|
||||
- name: Setup Emscripten toolchain
|
||||
uses: mymindstorm/setup-emsdk@v10
|
||||
uses: emscripten-core/setup-emsdk@v16
|
||||
with:
|
||||
version: ${{ env.EM_VERSION }}
|
||||
actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
|
||||
|
||||
- name: Work around emscripten-core/emscripten#26534
|
||||
run: npm install -g typescript@5
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/wasm
|
||||
cd out/wasm
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../../
|
||||
make
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ../../
|
||||
make -j4
|
||||
mkdir xsystem35
|
||||
mv src/xsystem35.* xsystem35/
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xsystem35-wasm
|
||||
path: out/wasm/xsystem35
|
||||
|
||||
@@ -10,20 +10,33 @@ jobs:
|
||||
|
||||
name: Linux ${{ matrix.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev
|
||||
sudo apt install libsdl2-dev libfreetype-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/${{ matrix.build-type }}
|
||||
cd out/${{ matrix.build-type }}
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} ../../
|
||||
make
|
||||
make -j4
|
||||
|
||||
- name: Test
|
||||
run: ctest --output-on-failure
|
||||
working-directory: out/${{ matrix.build-type }}
|
||||
|
||||
- name: Verify xsystem35.pot is up to date
|
||||
if: matrix.build-type == 'Debug'
|
||||
run: |
|
||||
xgettext --default-domain=xsystem35 --directory=. \
|
||||
--keyword=_ --keyword=N_ \
|
||||
--files-from=po/POTFILES.in \
|
||||
--output=/tmp/fresh.pot
|
||||
if ! diff <(grep '^msgid' po/xsystem35.pot | sort) \
|
||||
<(grep '^msgid' /tmp/fresh.pot | sort); then
|
||||
echo "::error::po/xsystem35.pot is out of date. Build the 'pot' target and commit the changes under po/."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -3,58 +3,69 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mingw: ["MINGW32", "MINGW64"]
|
||||
include:
|
||||
- mingw: "MINGW32"
|
||||
package: "mingw-w64-i686"
|
||||
installer: "xsystem35-32bit"
|
||||
- mingw: "MINGW64"
|
||||
package: "mingw-w64-x86_64"
|
||||
installer: "xsystem35-64bit"
|
||||
- sys: mingw32
|
||||
package: "xsystem35-32bit"
|
||||
cmake_args: "-DENABLE_DEBUGGER=no"
|
||||
deps: ""
|
||||
- sys: ucrt64
|
||||
package: "xsystem35-64bit"
|
||||
cmake_args: ""
|
||||
deps: >-
|
||||
cjson:p
|
||||
portmidi:p
|
||||
defaults:
|
||||
run:
|
||||
shell: bash.exe --login -eo pipefail "{0}"
|
||||
env:
|
||||
MSYSTEM: ${{ matrix.mingw }}
|
||||
CHERE_INVOKING: 1
|
||||
name: MSYS2 ${{ matrix.mingw }}
|
||||
shell: msys2 {0}
|
||||
name: MSYS2 ${{ matrix.sys }}
|
||||
steps:
|
||||
|
||||
- name: Set up shell
|
||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||
shell: pwsh
|
||||
|
||||
- name: Print system version
|
||||
run: |
|
||||
uname
|
||||
echo PATH: $PATH
|
||||
|
||||
- name: Install Deps
|
||||
run: pacman -S --noconfirm ${{ matrix.package }}-SDL2 ${{ matrix.package }}-SDL2_ttf ${{ matrix.package }}-SDL2_mixer ${{ matrix.package }}-libjpeg-turbo ${{ matrix.package }}-libwebp ${{ matrix.package }}-cjson ${{ matrix.package }}-nsis ${{ matrix.package }}-ntldd-git
|
||||
- name: Set up MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
pacboy: >-
|
||||
SDL2:p
|
||||
freetype:p
|
||||
SDL2_mixer:p
|
||||
libwebp:p
|
||||
${{ matrix.deps }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Copy licenses
|
||||
run: |
|
||||
ls /${{ matrix.sys }}/share/licenses
|
||||
mkdir dist
|
||||
cp -r /${{ matrix.sys }}/share/licenses dist/
|
||||
cp licenses/* dist/licenses/
|
||||
|
||||
- name: Install dev dependencies
|
||||
run: pacboy --noconfirm -S --needed gcc:p cmake:p ninja:p asciidoctor:p
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/release
|
||||
cd out/release
|
||||
cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release ../../
|
||||
make
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ${{ matrix.cmake_args }} ../../
|
||||
ninja
|
||||
|
||||
- name: Test
|
||||
run: ctest --output-on-failure
|
||||
working-directory: out/release
|
||||
|
||||
- name: Package
|
||||
working-directory: tools/installer
|
||||
run: ./make-installer.sh ../../out/release ${{ matrix.installer }}.nsi
|
||||
run: |
|
||||
cp out/release/src/xsystem35.exe out/release/doc/*.html *.md COPYING xsys35rc.sample dist/
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.installer }}
|
||||
path: tools/installer/${{ matrix.installer }}.exe
|
||||
name: ${{ matrix.package }}
|
||||
path: dist
|
||||
compression-level: 9 # because we use it for releases
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
out
|
||||
android/.gradle
|
||||
android/.idea
|
||||
android/app/.cxx
|
||||
android/app/.externalNativeBuild
|
||||
android/app/build
|
||||
android/app/src/main/assets
|
||||
android/build
|
||||
android/keystore.jks
|
||||
android/local.properties
|
||||
.ccls-cache
|
||||
.dir-locals.el
|
||||
compile_commands.json
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
# Changelog
|
||||
|
||||
## 2.20.0 - 2026-09-11
|
||||
- Improved font rendering to more closely match the original engine. On
|
||||
Windows, MS Gothic and MS Mincho are now used by default when available.
|
||||
- The experimental `enable_zb` option has been removed. (#44)
|
||||
- SACT: Message skipping now also skips text animation, effects, waits, and
|
||||
related input waits.
|
||||
- SACT: BGM now honors the loop count specified by the game.
|
||||
- SACT: Fixed timer handling in 楽園行.
|
||||
- The popup menu and dialogs can now be operated with the keyboard.
|
||||
- Fixed mouse coordinates changing unexpectedly when automatic mouse movement
|
||||
is disabled in System 3.8/3.9 games.
|
||||
- Android: The status bar now stays hidden after returning to the game. (#81)
|
||||
- Debugger: Added an `info cache` command for viewing cache usage and hit rates.
|
||||
|
||||
## 2.19.1 - 2026-08-02
|
||||
- Fixed an issue that prevented moving upward during battles in Rance 4.
|
||||
|
||||
## 2.19.0 - 2026-07-19
|
||||
- Replaced the GTK-based popup menu and dialogs with a pure-SDL implementation.
|
||||
- On Android, the popup menu opens with a three-finger tap.
|
||||
- Android: Added installation directly from CD-ROM images.
|
||||
- Implemented volume control panel.
|
||||
- Added `mute_on_unfocus` option to mute audio while the window is unfocused.
|
||||
|
||||
## 2.18.0 - 2026-06-23
|
||||
- Added support for the intro demo of Daiakuji.
|
||||
- Replaced the bundled mincho font with IPA Mincho.
|
||||
|
||||
## 2.17.1 - 2026-01-10
|
||||
- Fixed an issue where Rance 5D could not save files on non-Windows systems.
|
||||
- Improved fast-forward behavior using the Ctrl key in Rance 5D.
|
||||
- The `SI 2` command now returns 0 if a playlist file is not provided.
|
||||
|
||||
## 2.17.0 - 2025-11-09
|
||||
- Added "Streamer Mode" to mosaic specified (NSFW) images. This is enabled via the `-censor <file>` command-line option.
|
||||
- Windows: Fixed an issue where Rance 5D could not save files if the `Save` directory did not exist.
|
||||
|
||||
## 2.16.1 - 2025-08-28
|
||||
- Fixed text rendering issue in SACT log (#71)
|
||||
- Windows: The 32-bit version no longer supports the portmidi MIDI backend
|
||||
|
||||
## 2.16.0 - 2025-06-13
|
||||
- Fixed map tile color issue in Graymerca (#67)
|
||||
- Windows: Implemented number input dialog for the `NI` command (#68)
|
||||
- Android: Added "Clear Save Files" launcher menu command
|
||||
|
||||
## 2.15.1 - 2025-05-14
|
||||
- Fixed a text rendering issue in Rance 5D (#66)
|
||||
|
||||
## 2.15.0 - 2025-04-21
|
||||
- Fixed color palette bug introduced in v2.14.2.
|
||||
- Fixed uncancellable message waiting mode.
|
||||
- Android: "Install from ZIP" button is moved to the top-right option menu.
|
||||
- Android: Improved extraction logic of track numbers from audio file names.
|
||||
|
||||
## 2.14.3 - 2025-04-01
|
||||
- Fixed build for big endian architectures.
|
||||
|
||||
## 2.14.2 - 2025-03-09
|
||||
- Android: Back button to exit the game (#50)
|
||||
- Improved touch input usability in ママトト (#64)
|
||||
|
||||
## 2.14.1 - 2025-02-07
|
||||
- Fixed font selection and text decoration not working in selection menus.
|
||||
- Fixed a graphic issue in DALK intro.
|
||||
|
||||
## 2.14.0 - 2025-01-09
|
||||
- New supported games: 俺の下であがけ and 楽園行.
|
||||
- Windows: On Windows 10 version 1903 or above, now xsystem35 can handle
|
||||
Japanese filenames correctly even if the system locale is not Japanese.
|
||||
|
||||
## 2.13.0 - 2024-12-07
|
||||
- Windows: `Option` -> `Auto Copy Text to Clipboard` menu command has been added.
|
||||
When enabled, in-game text will be copied to the clipboard automatically. This
|
||||
can be used with text hooker tools that support clipboard monitoring.
|
||||
|
||||
## 2.12.1 - 2024-12-02
|
||||
- Windows: Fixed missing DLL error (#60)
|
||||
|
||||
## 2.12.0 - 2024-11-26
|
||||
- Implemented playback of the opening movie for 妻みぐい2.
|
||||
|
||||
## 2.11.7 - 2024-11-12
|
||||
- Fixed crash when canceling menu with two-finger touch (#58)
|
||||
- Fixed screen artifact in Rance 3 immediately after selecting "Game Start"
|
||||
|
||||
## 2.11.6 - 2024-07-22
|
||||
- Fixed strange behavior when canceling the battle selection menu in Kichikuou Rance. (#56)
|
||||
- Use system mouse cursors whenever possible. They look better on high dpi displays.
|
||||
- Added manual page for the xsystem35 command.
|
||||
|
||||
## 2.11.5 - 2024-06-10
|
||||
- Android: Fixed a black screen issue in the MangaGamer version of Rance 5D.
|
||||
- Fixed `fileCheckExist` command. (#55)
|
||||
|
||||
## 2.11.4 - 2024-05-22
|
||||
- Windows: In full-screen mode, the menu bar now appears only when the mouse pointer is at the top of the screen. (#53)
|
||||
- Fixed text rendering issue in Rance 3 intro. (#54)
|
||||
|
||||
## 2.11.3 - 2024-04-11
|
||||
- Android: Fixed a bug where save files from older versions could not be loaded in version 2.11.2.
|
||||
|
||||
## 2.11.2 - 2024-04-10
|
||||
- Added `-savedir` option to specify save directory.
|
||||
- Android: Fixed a bug where .xsys35rc in a subdirectory of ZIP was not loaded. (#51)
|
||||
|
||||
## 2.11.1 - 2024-02-18
|
||||
- Windows: Fixed a black screen issue in the MangaGamer version of Rance 5D (#45).
|
||||
- Fixed a crash bug in command-line debugger.
|
||||
|
||||
## 2.11.0 - 2024-01-20
|
||||
- Windows: Now `xsystem35.exe` is a standalone executable. You can just copy it to the game folder and run it.
|
||||
- Windows: The installer is no longer provided. If you have an earlier version installed, please uninstall it.
|
||||
- Windows: The debugger is no longer available in the 32-bit version. Please use the 64-bit version if you need it.
|
||||
- Added experimental `enable_zb` option (#44). See `xsys35rc.sample` for details.
|
||||
|
||||
## 2.10.1 - 2024-01-01
|
||||
- Windows: Added "Integer Scaling" menu option
|
||||
- Fixed initial palette colors (to match System3.9)
|
||||
- Fixed a bug where palette 246 to 249 were unintentionally changed after loading 256-color CGs
|
||||
|
||||
## 2.10.0 - 2023-12-09
|
||||
- Breaking change: A [bug](https://github.com/kichikuou/xsystem35-sdl2/issues/41) has been fixed in which save format for 大悪司 and かえるにょ国にょアリス was not compatible with System3.9. As a result, save files for these games created with older versions of xsystem35 are no longer usable. (Loading will not fail, but wrong values will be loaded.)
|
||||
- Now the 64-bit executable supports Windows 10 or later. For older Windows, please use the 32-bit version.
|
||||
- Fixed a problem with the download edition of Daiakuji requiring insertion of Disk 2. (#43)
|
||||
- Debugger: Added debug commands for monitoring the color palette.
|
||||
- Minor bug fixes.
|
||||
|
||||
## 2.9.1 - 2023-06-22
|
||||
- Fixed a bug where the `CX 1` command (copy with transparent color) did not work in some games (e.g. Daiakuji)
|
||||
- Debugger: Now breakpoints do not make the window completely unresponsive
|
||||
- Debugger: Fixed a conditional breakpoint crash bug
|
||||
|
||||
## 2.9.0 - 2023-05-04
|
||||
- Now xsystem35 uses the System3.9 save file format (unless `-saveformat` command line flag is specified). Old save files can still be loaded.
|
||||
- Changed the default naming convention for save files from `[a-z]sleep.asd` to `<gamename>s[a-z].asd`.
|
||||
- Fixed a bug where SACT games (e.g. Rance 5D) were not responding to touch.
|
||||
- Fixed memory leaks in debugger.
|
||||
|
||||
## 2.8.0 - 2023-02-12
|
||||
- Added support for Rance 4.1/4.2 ver1.05
|
||||
- Fixed movement animation in rooms in Rance 4 v2.05 (#35)
|
||||
- Added `-game` option which can be used to enable game-specific hacks in translated games
|
||||
|
||||
## 2.7.0 - 2023-01-28
|
||||
- Added support for Rance4 ver.2.05
|
||||
- Now supports BGM playback for the download edition of Daiakuji
|
||||
- Bug fixes
|
||||
|
||||
## 2.6.0 - 2023-01-01
|
||||
- Android: Introduced a new way to simulate right-click; tapping on the black bars at the left/right or top/bottom of the screen is treated as a right click. (Two-finger touch, the old way to simulate a right click, still works.)
|
||||
- Windows: Added a menu command to enable/disable automatic mouse movements.
|
||||
|
||||
## 2.5.1 - 2022-07-31
|
||||
- Implemented "palette shift" graphic effect (used in 闘神都市II).
|
||||
- Implemented `grDrawFillCircle` command (used in グレイメルカ).
|
||||
- Fixed color update bug in 256-color games.
|
||||
- Fixed crash by out-of-bounds variable access in debugger.
|
||||
|
||||
## 2.5.0 - 2022-07-17
|
||||
- Supported JPEG image format on Android.
|
||||
|
||||
## 2.4.0 - 2022-07-02
|
||||
- Windows: Added "Restart" menu command.
|
||||
- Fixed a bug where wrong music is played in Kichikuou Rance (System3.9 version). #32
|
||||
- Supported mouse wheel input.
|
||||
- Fixed a bug in `MF` command.
|
||||
- Implemented `grEffectMoveView` command.
|
||||
|
||||
## 2.3.0 - 2022-04-08
|
||||
- Code for screen transition effects have been substantially rewritten (but you probably wouldn't notice any difference).
|
||||
- Fixed bugs in System3.9 games.
|
||||
- Android: Enabled joystick input.
|
||||
- Debugger improvements.
|
||||
|
||||
## 2.2.0 - 2021-12-03
|
||||
From now on, the Android and Windows versions will use a common version number.
|
||||
|
||||
- Android: Use custom fonts specified in `.xsys35c`. (#20)
|
||||
- Supported "王子さまLv1".
|
||||
- Debugger improvements.
|
||||
- Fixed some bugs and compatibility issues.
|
||||
|
||||
## 2.1.0 - 2021-11-07
|
||||
- Fixed issues with MangaGamer version of Rance 5D.
|
||||
- Fixed garbled text in some System3.9 games with Unicode mode.
|
||||
- Display a message box on fatal errors.
|
||||
- Various debugger improvements.
|
||||
|
||||
## 2.0.1 - 2021-10-24
|
||||
- Fixed crash during shutdown.
|
||||
|
||||
## 2.0.0 - 2021-10-23
|
||||
- Added window menubar from which you can save screenshots, change fullscreen mode, and enable message skipping.
|
||||
- Added debugger support that can be used from Visual Studio Code. See [vscode-system3x](https://github.com/kichikuou/vscode-system3x) for details.
|
||||
- Many bug fixes and compatibility fixes.
|
||||
+127
-121
@@ -1,19 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
cmake_minimum_required(VERSION 3.13...4.0)
|
||||
|
||||
project(xsystem35 LANGUAGES C)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
enable_testing()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
|
||||
set(XSYSTEM35_VERSION "2.20.0")
|
||||
|
||||
include(CheckIncludeFile)
|
||||
include(CheckLibraryExists)
|
||||
include(CheckSymbolExists)
|
||||
|
||||
# In msys, `pkg-config --libs` returns `-lmingw32` but find_library() cannot
|
||||
# find it in default search paths.
|
||||
if (MSYS)
|
||||
list(APPEND CMAKE_LIBRARY_PATH $ENV{MINGW_PREFIX}/$ENV{MINGW_CHOST}/lib)
|
||||
endif ()
|
||||
include(FetchContent)
|
||||
|
||||
# A wrapper around pkg_check_modules() that skips the check if ENABLE_<prefix> is false
|
||||
macro (optional_pkg_check_modules prefix)
|
||||
@@ -23,181 +18,189 @@ macro (optional_pkg_check_modules prefix)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Generates a static library from pkg_check_modules() result
|
||||
function(add_static_library name pkg)
|
||||
add_library(${name} INTERFACE)
|
||||
set_target_properties(${name}
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${${pkg}_STATIC_INCLUDE_DIRS}"
|
||||
INTERFACE_COMPILE_OPTIONS "${${pkg}_STATIC_CFLAGS_OTHER}"
|
||||
INTERFACE_LINK_LIBRARIES "${${pkg}_STATIC_LIBRARIES}"
|
||||
INTERFACE_LINK_OPTIONS "${${pkg}_STATIC_LDFLAGS_OTHER}")
|
||||
endfunction()
|
||||
|
||||
function(fetch_webp)
|
||||
FetchContent_Declare(
|
||||
libwebp
|
||||
URL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.5.0.tar.gz
|
||||
URL_HASH SHA1=b21aa842136dc59a72a38776a5aa73f4d0b00ac5
|
||||
)
|
||||
set(WEBP_BUILD_ANIM_UTILS OFF CACHE BOOL "Build animation utilities." FORCE)
|
||||
set(WEBP_BUILD_CWEBP OFF CACHE BOOL "Build the cwebp command line tool." FORCE)
|
||||
set(WEBP_BUILD_DWEBP OFF CACHE BOOL "Build the dwebp command line tool." FORCE)
|
||||
set(WEBP_BUILD_GIF2WEBP OFF CACHE BOOL "Build the gif2webp conversion tool." FORCE)
|
||||
set(WEBP_BUILD_IMG2WEBP OFF CACHE BOOL "Build the img2webp animation tool." FORCE)
|
||||
set(WEBP_BUILD_VWEBP OFF CACHE BOOL "Build the vwebp viewer tool." FORCE)
|
||||
set(WEBP_BUILD_WEBPINFO OFF CACHE BOOL "Build the webpinfo command line tool." FORCE)
|
||||
set(WEBP_BUILD_LIBWEBPMUX OFF CACHE BOOL "Build the libwebpmux library." FORCE)
|
||||
set(WEBP_BUILD_WEBPMUX OFF CACHE BOOL "Build the webpmux command line tool." FORCE)
|
||||
set(WEBP_BUILD_EXTRAS OFF CACHE BOOL "Build extras." FORCE)
|
||||
set(WEBP_BUILD_WEBP_JS OFF CACHE BOOL "Emscripten build of webp.js." FORCE)
|
||||
FetchContent_MakeAvailable(libwebp)
|
||||
add_library(WebP ALIAS webp)
|
||||
endfunction()
|
||||
|
||||
check_symbol_exists(getlogin "unistd.h" HAVE_GETLOGIN)
|
||||
check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
|
||||
check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
|
||||
check_symbol_exists(uname "sys/utsname.h" HAVE_UNAME)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(HAVE_JPEG 1)
|
||||
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
|
||||
|
||||
function(add_emscripten_library name option)
|
||||
add_library(${name} INTERFACE)
|
||||
set_target_properties(${name} PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS ${option}
|
||||
INTERFACE_LINK_OPTIONS ${option})
|
||||
endfunction()
|
||||
add_emscripten_library(zlib -sUSE_ZLIB=1)
|
||||
add_emscripten_library(sdl2 -sUSE_SDL=2)
|
||||
add_emscripten_library(freetype2 -sUSE_FREETYPE=1)
|
||||
set(DEFAULT_FONT_PATH /fonts/)
|
||||
fetch_webp()
|
||||
set(HAVE_WEBP 1)
|
||||
|
||||
elseif (ANDROID)
|
||||
set(SDL2MIXER_FOUND 1)
|
||||
find_library(ndk_zlib z)
|
||||
add_library(sdl2 ALIAS SDL2)
|
||||
add_library(freetype2 ALIAS freetype)
|
||||
add_library(sdl2_mixer ALIAS SDL2_mixer)
|
||||
find_library(ndk_log log)
|
||||
else()
|
||||
find_library(ndk_zlib z)
|
||||
add_library(zlib INTERFACE)
|
||||
set_target_properties(zlib PROPERTIES INTERFACE_LINK_LIBRARIES ${ndk_zlib})
|
||||
fetch_webp()
|
||||
set(HAVE_WEBP 1)
|
||||
|
||||
else() # non-emscripten, non-android
|
||||
if (WIN32)
|
||||
set(ZLIB_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
find_package(ZLIB REQUIRED)
|
||||
add_library(zlib ALIAS ZLIB::ZLIB)
|
||||
|
||||
include(FindPkgConfig)
|
||||
optional_pkg_check_modules(GTK3 IMPORTED_TARGET gtk+-3.0)
|
||||
if (GTK3_FOUND)
|
||||
set(ENABLE_GTK 1)
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
|
||||
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
|
||||
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
|
||||
if (WIN32)
|
||||
add_static_library(sdl2 SDL2)
|
||||
add_static_library(freetype2 FREETYPE)
|
||||
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
|
||||
target_link_libraries(freetype2 INTERFACE -lstdc++)
|
||||
add_static_library(sdl2_mixer SDL2MIXER)
|
||||
else()
|
||||
add_library(sdl2 ALIAS PkgConfig::SDL2)
|
||||
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
|
||||
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
|
||||
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(SDL2 IMPORTED_TARGET sdl2)
|
||||
pkg_check_modules(SDL2TTF IMPORTED_TARGET SDL2_ttf)
|
||||
optional_pkg_check_modules(SDL2MIXER IMPORTED_TARGET SDL2_mixer)
|
||||
|
||||
option(ENABLE_DEBUGGER "Enable built-in debugger" ON)
|
||||
if (ENABLE_DEBUGGER)
|
||||
pkg_check_modules(cJSON IMPORTED_TARGET libcjson)
|
||||
if (NOT cJSON_FOUND)
|
||||
if (cJSON_FOUND)
|
||||
if (WIN32)
|
||||
add_static_library(cJSON cJSON)
|
||||
else ()
|
||||
add_library(cJSON ALIAS PkgConfig::cJSON)
|
||||
endif()
|
||||
else ()
|
||||
# libcjson-dev of Debian buster / Ubuntu 20.04 does not install pkgconfig files.
|
||||
find_library(cJSON cjson)
|
||||
if (NOT cJSON)
|
||||
message(FATAL_ERROR "libcjson is required but not found.")
|
||||
endif()
|
||||
add_library(cJSON ALIAS ${cJSON})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(FindJPEG)
|
||||
if (JPEG_FOUND)
|
||||
set(HAVE_JPEG 1)
|
||||
endif()
|
||||
|
||||
optional_pkg_check_modules(WEBP IMPORTED_TARGET libwebp)
|
||||
if (WEBP_FOUND)
|
||||
set(HAVE_WEBP 1)
|
||||
endif()
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads)
|
||||
if (CMAKE_USE_PTHREADS_INIT)
|
||||
find_library(PORTMIDI portmidi)
|
||||
if (PORTMIDI)
|
||||
set(HAVE_PORTMIDI 1)
|
||||
set(ENABLE_MIDI_PORTMIDI 1)
|
||||
if (WIN32)
|
||||
add_static_library(WebP WEBP)
|
||||
else()
|
||||
add_library(WebP ALIAS PkgConfig::WEBP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
|
||||
if (WIN32)
|
||||
optional_pkg_check_modules(PORTMIDI IMPORTED_TARGET portmidi)
|
||||
if (PORTMIDI_FOUND)
|
||||
set(HAVE_PORTMIDI 1)
|
||||
add_static_library(portmidi_static PORTMIDI)
|
||||
set(PORTMIDI portmidi_static)
|
||||
endif()
|
||||
else ()
|
||||
find_library(PORTMIDI portmidi)
|
||||
if (PORTMIDI)
|
||||
set(HAVE_PORTMIDI 1)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Menu
|
||||
# i18n support
|
||||
#
|
||||
# Translate via libintl if available; otherwise fall back to a built-in catalog
|
||||
# compiled from the .po files.
|
||||
#
|
||||
# To add a language, create po/<lang>.po and list <lang> here.
|
||||
set(NLS_LANGUAGES ja)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(SRC_MENU menu_emscripten.c)
|
||||
elseif (ANDROID)
|
||||
set(SRC_MENU menu_android.c)
|
||||
elseif (ENABLE_GTK)
|
||||
set(SRC_MENU menu.c menu_callback.c menu_gui.c s39init.c)
|
||||
|
||||
# i18n support (currently only menus are translated)
|
||||
if (NOT EMSCRIPTEN AND NOT ANDROID AND NOT WIN32)
|
||||
include(FindIntl)
|
||||
include(FindGettext)
|
||||
if (Intl_FOUND AND GETTEXT_FOUND)
|
||||
set(ENABLE_NLS 1)
|
||||
set(HAVE_LIBINTL 1)
|
||||
add_compile_definitions(LOCALEDIR="${CMAKE_INSTALL_PREFIX}/share/locale")
|
||||
include_directories(${Intl_INCLUDE_DIRS})
|
||||
link_libraries(${Intl_LIBRARIES})
|
||||
endif()
|
||||
else ()
|
||||
set(SRC_MENU menu_sdl.c sdl_input.c)
|
||||
endif()
|
||||
|
||||
# PCM audio
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
list(APPEND SRC_AUDIO)
|
||||
list(APPEND SRC_AUDIO pcm.emscripten.c bgm.emscripten.c bgi.c)
|
||||
list(APPEND SUMMARY_AUDIO "Emscripten")
|
||||
elseif (SDL2MIXER_FOUND)
|
||||
list(APPEND SRC_AUDIO pcm.sdlmixer.c bgm.sdlmixer.c bgi.c)
|
||||
list(APPEND SUMMARY_AUDIO "SDL_mixer")
|
||||
set(ENABLE_SDLMIXER 1)
|
||||
else()
|
||||
list(APPEND SRC_AUDIO pcm.dummy.c bgm.dummy.c)
|
||||
list(APPEND SUMMARY_AUDIO "SDL_mixer")
|
||||
endif()
|
||||
|
||||
# CDROM
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
list(APPEND SRC_CDROM cdrom.Linux.c)
|
||||
list(APPEND SUMMARY_CDROM "Linux ioctl")
|
||||
set(ENABLE_CDROM_LINUX 1)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
list(APPEND SRC_CDROM cdrom.FreeBSD.c)
|
||||
list(APPEND SUMMARY_CDROM "*BSD ioctl")
|
||||
set(ENABLE_CDROM_BSD 1)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
list(APPEND SRC_CDROM cdrom.emscripten.c)
|
||||
if (EMSCRIPTEN)
|
||||
list(APPEND SUMMARY_CDROM "Emscripten")
|
||||
set(ENABLE_CDROM_EMSCRIPTEN 1)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
list(APPEND SRC_CDROM cdrom.android.c)
|
||||
list(APPEND SUMMARY_CDROM "Android")
|
||||
set(ENABLE_CDROM_ANDROID 1)
|
||||
else()
|
||||
list(APPEND SRC_CDROM cdrom.empty.c)
|
||||
endif()
|
||||
if (SDL2MIXER_FOUND AND NOT ANDROID)
|
||||
list(APPEND SRC_CDROM cdrom.mp3.c)
|
||||
list(APPEND SUMMARY_CDROM "SDL_mixer (wav|mp3|ogg...)")
|
||||
set(ENABLE_CDROM_MP3 1)
|
||||
endif()
|
||||
set(CDROM_DEVICE "playlist.txt" CACHE STRING "CDROM Device Name")
|
||||
set(DEFAULT_PLAYLIST_PATH "playlist.txt" CACHE STRING "Default playlist path")
|
||||
|
||||
# MIDI
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
list(APPEND SRC_MIDI midi.emscripten.c)
|
||||
list(APPEND SUMMARY_MIDI "Emscripten")
|
||||
elseif (ANDROID)
|
||||
list(APPEND SRC_MIDI midi.android.c)
|
||||
list(APPEND SUMMARY_MIDI "Android")
|
||||
else()
|
||||
if (HAVE_SIGACTION)
|
||||
list(APPEND SRC_MIDI midi.rawmidi.c midifile.c)
|
||||
list(APPEND SUMMARY_MIDI "raw")
|
||||
set(MIDI_DEVICE "/dev/midi" CACHE STRING "midi device")
|
||||
set(ENABLE_MIDI_RAWMIDI 1)
|
||||
option(QUIET_MIDI "No display midi stdout" OFF)
|
||||
endif()
|
||||
list(APPEND SUMMARY_MIDI "SDL_mixer")
|
||||
set(ENABLE_MIDI_SDLMIXER 1)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
check_symbol_exists(SNDCTL_SEQ_NRSYNTHS "machine/soundcard.h" ENABLE_MIDI_SEQMIDI)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "(NetBSD|OpenBSD)")
|
||||
check_symbol_exists(SNDCTL_SEQ_NRSYNTHS "soundcard.h" ENABLE_MIDI_SEQMIDI)
|
||||
else ()
|
||||
check_symbol_exists(SNDCTL_SEQ_NRSYNTHS "sys/soundcard.h" ENABLE_MIDI_SEQMIDI)
|
||||
endif()
|
||||
|
||||
if (ENABLE_MIDI_SEQMIDI)
|
||||
list(APPEND SUMMARY_MIDI "OSS sequencer")
|
||||
set(SEQ_DEVICE "/dev/sequencer" CACHE STRING "sequencer device")
|
||||
endif()
|
||||
|
||||
if (SDL2MIXER_FOUND)
|
||||
list(APPEND SRC_MIDI midi.sdlmixer.c)
|
||||
list(APPEND SUMMARY_MIDI "SDL_mixer")
|
||||
set(ENABLE_MIDI_SDLMIXER 1)
|
||||
endif()
|
||||
|
||||
if (HAVE_PORTMIDI AND ENABLE_MIDI_PORTMIDI)
|
||||
list(APPEND SRC_MIDI midi.portmidi.c)
|
||||
if (HAVE_PORTMIDI)
|
||||
list(APPEND SUMMARY_MIDI "PortMidi")
|
||||
link_libraries(${PORTMIDI} Threads::Threads)
|
||||
set(ENABLE_MIDI_PORTMIDI 1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Joystick
|
||||
|
||||
if (NOT EMSCRIPTEN AND NOT ANDROID)
|
||||
set(HAVE_SDLJOY 1)
|
||||
endif()
|
||||
|
||||
# TODO: MMX
|
||||
|
||||
function (print_summary name)
|
||||
if (ARGN)
|
||||
list(JOIN ARGN ", " MSG)
|
||||
@@ -212,21 +215,24 @@ message("------------------")
|
||||
print_summary(audio ${SUMMARY_AUDIO})
|
||||
print_summary(cdrom ${SUMMARY_CDROM})
|
||||
print_summary(midi ${SUMMARY_MIDI})
|
||||
print_summary(joystick ${SUMMARY_JOYSTICK})
|
||||
message("------------------")
|
||||
|
||||
set(PACKAGE ${PROJECT_NAME})
|
||||
configure_file(config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_compile_definitions(HAVE_CONFIG_H)
|
||||
add_compile_definitions($<$<CONFIG:Debug>:DEBUG>)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(fonts)
|
||||
if (ENABLE_NLS)
|
||||
add_subdirectory(po)
|
||||
if (NOT ANDROID AND NOT EMSCRIPTEN)
|
||||
add_subdirectory(doc)
|
||||
endif()
|
||||
add_subdirectory(po)
|
||||
|
||||
add_subdirectory(modules)
|
||||
target_link_libraries(xsystem35 PRIVATE modules)
|
||||
|
||||
if (NOT ANDROID AND NOT EMSCRIPTEN)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
|
||||
System 3.5 for X Window System Version 1.7 (開発版)
|
||||
|
||||
インストールドキュメント
|
||||
|
||||
|
||||
0. 目次
|
||||
|
||||
1. 必要環境と対応機能
|
||||
2. コンパイルとインストール
|
||||
3. コンパイル時のオプション
|
||||
4. サポート
|
||||
|
||||
|
||||
1. 必要環境と対応機能
|
||||
|
||||
1.1 必要環境
|
||||
* X11R6
|
||||
* gtk+ 2.0.0 以上
|
||||
* SYSV IPC in kernel
|
||||
* zlib
|
||||
* Ogg Vorbis library (libvorbis)
|
||||
|
||||
1.2 推奨環境
|
||||
* スケーラブルフォント(例えば X-TT など)
|
||||
(http://X-TT.dsl.gr.jp/index-ja.html)
|
||||
* MIT-SHM extension をサポートした X Server。次のようにチェックします。
|
||||
$ xdpyinfo | grep MIT-SHM
|
||||
* MMX Pentinum 以降 (for IA32 platform)
|
||||
|
||||
1.3 対応機能
|
||||
* ALSA (for Linux) 0.9.x 以上
|
||||
(http://www.alsa-project.org/)
|
||||
* ESD (Esound) 0.2.8 以上
|
||||
(http://www.tux.org/~ricdude/EsounD.html)
|
||||
(ftp://ftp.gnome.org/pub/GNOME/stable/sources/esound/ or mirrors)
|
||||
* Joystick (for Linux kernel 2.2.x)
|
||||
* SDL(Simple DirectMedia Layer) 1.0.x 以上
|
||||
(http://www.devolution.com/~slouken/SDL)
|
||||
* FreeType Library 2.0 以上
|
||||
(http://www.freetype.org/)
|
||||
* midi デバイス(OSS /dev/midi, /dev/sequencer)を介した外部/仮想MIDI音源
|
||||
|
||||
1.4 注意点
|
||||
* コンパイルには GNU Make (gmake) と gtk-config と nasm (assembler)
|
||||
が必要です。(nasm は IA-32 platform のみ)
|
||||
* また gcc 以外でのコンパイルは多分出来ないと思います。標準 cc でコンパイル
|
||||
を通すためのパッチは大歓迎です。
|
||||
|
||||
|
||||
2. コンパイルとインストール
|
||||
|
||||
2.1 プログラムのコンパイルとインストール
|
||||
|
||||
$ tar xvfz xsystem35-(version).tar.gz # ソースコードの展開
|
||||
$ cd xsystem35-(version) # xsystem35 のトップディレクトリに移動
|
||||
$ cp src/xsys35rc.sample ~/.xsys35rc # 設定ファイルのコピー
|
||||
$ ./configure # `configure' スクリプトの実行
|
||||
$ make # xsystem35 のコンパイル
|
||||
[必要なら root になる]
|
||||
# make install # xsystem35 のインストール
|
||||
|
||||
default では make install で /usr/local/bin にインストールされます。
|
||||
|
||||
2.2 ゲームデータのインストール
|
||||
|
||||
contrib ディレクトリにゲームデータをインストールするプログラムが入っています。
|
||||
/cdromにCD-ROMをマウントした後、
|
||||
|
||||
$ cd ./contrib
|
||||
$ ./instgame kichiku.inf
|
||||
|
||||
のようにしてインストールします。 詳しくは contrib/README.TXTを御覧下さい。
|
||||
|
||||
|
||||
3. コンパイル時のオプション
|
||||
|
||||
$ ./configure --help
|
||||
で使用可能なオプションの一覧がでます。
|
||||
|
||||
* audio 関連
|
||||
|
||||
--enable-audio=xxx
|
||||
オーディオデバイスのうち xxx を有効にしてコンパイルします。xxx には今の所
|
||||
oss, alsa, esd が使えます。カンマで区切って並べて指定する事で複数のデバイ
|
||||
スをゲーム起動時に選択できます。(ex. --enable-audio=oss,alsa,esd)
|
||||
このオプションを指定しない時は OS ごとに適切なデバイスが組み込まれます。
|
||||
|
||||
--with-default-output=xxx
|
||||
--enable-audioで複数のデバイスを選択した時、デフォルトのデバイスをこの
|
||||
オプションで指定できます。指定しない場合は使用可能なデバイスのうち任意の
|
||||
デバイスを使用します。
|
||||
|
||||
--with-oss-audiodev=xxx
|
||||
OSS での DSP デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/dsp です。(*1)
|
||||
|
||||
--with-oss-mixerdev=xxx
|
||||
OSS での MIXER デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/mixer です。(*1)
|
||||
|
||||
--enable-cdrom=xxx
|
||||
CD-ROM デバイスのうち xxx を有効にしてコンパイルします。CD-DA の代わりに
|
||||
MP3などを外部プレイヤを使って演奏することも出来ます。xxx には linux,bsd,
|
||||
mp3 が使えます。
|
||||
カンマで区切って並べて指定することで複数のデバイスをゲーム起動時に選択
|
||||
できます。(ex. --enable-cdrom=linux,mp3)
|
||||
CD-DA を使うか MP3 を使うかの指定については README.music を御覧下さい。
|
||||
このオプションを指定しない時は OS ごとに適切なデバイスが組み込まれます。
|
||||
|
||||
--with-cdromdev=xxx
|
||||
CD-ROM デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/cdrom です。(*1)
|
||||
|
||||
--enable-midi=xxx
|
||||
MIDI デバイスのうち xxx を有効にしてコンパイルします。xxx にはいまのところ
|
||||
extp,raw,seq が使えます。extp は外部プレイヤを使って MIDI を演奏します。
|
||||
raw は Raw midi デバイス (/dev/midiなど)を通して外部MIDI機器を接続している
|
||||
場合に使います。seq は OSS sequencer device (/dev/sequencer) を通して MIDI
|
||||
機器を接続している場合に使います。カンマで区切って並べて指定することで複数
|
||||
のデバイスをゲーム起動時に選択できます。(ex. --enable-midi=extp,raw,seq)
|
||||
|
||||
--with-mididev=xxx
|
||||
Raw MIDI デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/midi です。(*1)
|
||||
|
||||
--with-seqdev=xxx
|
||||
Sequencer MIDI デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/sequencer です。(*1)
|
||||
|
||||
--with-midiplayer=xxx
|
||||
外部 MIDI プレイヤを指定します。デフォルトは midiplay -4 です。(*1)
|
||||
|
||||
--enable-qmidi
|
||||
外部 MIDI プレイヤを実行する際に、プレイヤーの実行時メッセージを抑止
|
||||
します。
|
||||
|
||||
|
||||
* graphics 関連
|
||||
|
||||
--enable-sdl
|
||||
SDL を有効にしてコンパイルします。
|
||||
|
||||
--enable-bgr
|
||||
X server の color order が BGR の時指定します。SunとSGIのプラットフォーム
|
||||
ではデフォルトでこの指定が有効になっています。それ以外のプラットフォーム
|
||||
では不要です。ただし、sgi O2 および、Sun sparc のマシンの中には RGB の
|
||||
並びのものもあるので、その場合は --enable-bgr=no あるいは --disable-bgr
|
||||
としてください。詳しくは doc/README.color を御覧下さい。
|
||||
|
||||
|
||||
* その他
|
||||
|
||||
--enable-joy
|
||||
Joystic support を有効にしてコンパイルします。Linux kernel 2.2 以降に
|
||||
対応しています。
|
||||
|
||||
--with-joydev=xxx
|
||||
Joystick デバイスのデバイスファイルを xxx で指定します。デフォルトは
|
||||
/dev/js0 です。(*1)
|
||||
|
||||
--with-locale=xxxx
|
||||
X の locale 機構を使う場合に指定します。Linux libc5 System 等では通常
|
||||
使用している locale を指定して下さい。(ex. --with-locale=ja_JP.ujis)
|
||||
それ以外では必要無いと思います。
|
||||
|
||||
--with-cachesize=xxxx
|
||||
データキャッシュの大きさを指定します。単位は MB で、デフォルトは 20MB
|
||||
です。データのほとんどは展開済み CG のキャッシュに使われます。シナリオ
|
||||
などのデータは mmap を使っていますので、キャッシングは xsystem35 の管理
|
||||
外です。
|
||||
|
||||
--enable-mmx
|
||||
IA32 プラットフォームで MMX を使うようにします。nasm というアセンブラが
|
||||
必要になります。IA32 ではデフォルトで有効にします。無効にする場合は =no
|
||||
を指定して下さい。実行時に MMX 命令が使用可能かどうかチェックしますので、
|
||||
MMX 命令を持たないCPUでも有効のままでかまいません。
|
||||
|
||||
--enable-debug
|
||||
debuging を有効にします。
|
||||
有効にしてコンパイルすると -debuglv # オプションが使えるようになります。
|
||||
debug level に関しては src/xsystem35.h を参照して下さい。
|
||||
|
||||
## (*1) 実行時に読み込む設定ファイル ~/.xsys35rc でも指定できます。
|
||||
|
||||
|
||||
参考: 私の開発時の configure オプション(実際には1行です)
|
||||
|
||||
./configure --enable-midi=extp,raw,seq --enable-audio=oss,alsa,esd
|
||||
--enable-cdrom=linux,mp3 --enable-debug
|
||||
|
||||
4. サポート
|
||||
|
||||
http://www.aist-nara.ac.jp/~masaki-c/private/unitbase/xsys35/index.html
|
||||
または READMEの 8. サポートの項を御覧下さい。
|
||||
@@ -1,47 +1,153 @@
|
||||
# xsytem35-sdl2
|
||||
|
||||
This is a multi-platform port of xsystem35, a free implementation of AriceSoft's System3.x game engine.
|
||||
This is a multi-platform port of `xsystem35`, a free implementation of
|
||||
AliceSoft's System 3.x game engine.
|
||||
|
||||
## Download
|
||||
Prebuilt binaries for Windows and Android can be downloaded from the [Releases](https://github.com/kichikuou/xsystem35-sdl2/releases) page.
|
||||
## Compatibility
|
||||
|
||||
## Build
|
||||
See the [game compatibility table](game_compatibility.md) for a list of games
|
||||
that can be played with xsystem35-sdl2.
|
||||
|
||||
## Unique Features
|
||||
|
||||
In addition to the original System 3.x functionalities, xsystem35-sdl2 offers
|
||||
the following features:
|
||||
|
||||
### Playing Audio Files as Virtual CD Music
|
||||
|
||||
Many System 3.x games feature music as audio tracks on the CD-ROM. xsystem35
|
||||
can play music from audio files, eliminating the need to insert CDs. Supported
|
||||
audio file formats are MP3 and Ogg. To use ripped audio files, create a file
|
||||
named `playlist.txt` in the game directory and list the paths to your tracks,
|
||||
one per line. For example:
|
||||
|
||||
```
|
||||
# The first line is not used
|
||||
BGM/track02.mp3
|
||||
BGM/track03.mp3
|
||||
...
|
||||
```
|
||||
|
||||
The first line is not used because the first track on a game CD is typically a
|
||||
data track.
|
||||
|
||||
Some games have integrated music as MIDI. In such cases, the music won't play
|
||||
using the virtual CD feature. If you encounter a `Cannot load MIDI` error
|
||||
message, you might need to set the `SDL_SOUNDFONTS` environment variable to
|
||||
point to an `.sf2` file. For example:
|
||||
|
||||
```
|
||||
SDL_SOUNDFONTS=/usr/share/soundfonts/GeneralUser.sf2 xsystem35
|
||||
```
|
||||
|
||||
### Unicode Translation Support
|
||||
|
||||
While the original System 3.x only supported Shift_JIS (a Japanese character
|
||||
encoding), xsystem35 supports Unicode and can run games translated into
|
||||
languages other than Japanese and English.
|
||||
|
||||
For instructions on how to build a game with Unicode support, see the
|
||||
[xsys35c](https://github.com/kichikuou/xsys35c) documentation.
|
||||
|
||||
### Debugging
|
||||
|
||||
xsystem35 features a built-in debugger that allows you to step through the game
|
||||
and examine or modify game variables. There are two ways to use the debugger:
|
||||
|
||||
- Through [Visual Studio Code](https://code.visualstudio.com/) (recommended):
|
||||
The [vscode-system3x](https://github.com/kichikuou/vscode-system3x) extension
|
||||
provides a graphical debugging interface for System 3.x.
|
||||
- Using the CLI Debugger: Running xsystem35 with the `-debug` option will
|
||||
launch the debugger with a console interface. Type `help` to see a list of
|
||||
available commands.
|
||||
|
||||
### Streamer Mode
|
||||
|
||||
xsystem35 introduces a "Streamer Mode" to make playing games with NSFW content
|
||||
safer for streaming or public viewing. When enabled with the `-censor <file>`
|
||||
option, images specified in the provided file will be automatically mosaiced.
|
||||
|
||||
The `misc/censor/` directory contains example censor list files for some games.
|
||||
|
||||
## Installation
|
||||
|
||||
Prebuilt packages for Windows and Android can be downloaded from the
|
||||
[Releases](https://github.com/kichikuou/xsystem35-sdl2/releases) page.
|
||||
|
||||
Note for Windows:
|
||||
- The 64-bit version supports Windows 10 or later. For older versions of
|
||||
Windows, please use the 32-bit version.
|
||||
- Debugging is supported only in the 64-bit version.
|
||||
|
||||
For other platforms, refer to the [Building](#building) section.
|
||||
|
||||
## Running
|
||||
### Windows
|
||||
|
||||
Copy `xsystem35.exe` to the game folder and run it.
|
||||
|
||||
### Android
|
||||
|
||||
See [android/README.md](android/README.md#usage).
|
||||
|
||||
### Other Platforms
|
||||
|
||||
Run xsystem35 from within the game directory.
|
||||
|
||||
```bash
|
||||
$ cd /path/to/game_directory
|
||||
$ xsystem35
|
||||
```
|
||||
|
||||
See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
|
||||
|
||||
## Building
|
||||
### Linux (Debian / Ubuntu)
|
||||
|
||||
$ sudo apt install build-essential cmake libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libcjson-dev
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
```bash
|
||||
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
```
|
||||
|
||||
### MacOS
|
||||
|
||||
[Homebrew](https://brew.sh/index_ja) is needed.
|
||||
[Homebrew](https://brew.sh/) is required.
|
||||
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf libjpeg webp cjson
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
```bash
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi cjson asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
[MSYS2](https://www.msys2.org) is needed.
|
||||
[MSYS2](https://www.msys2.org) is required.
|
||||
|
||||
$ pacman -S cmake mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-libwebp mingw-w64-x86_64-cjson
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make
|
||||
```bash
|
||||
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make
|
||||
```
|
||||
|
||||
### Emscripten
|
||||
|
||||
$ mkdir -p out/wasm
|
||||
$ cd out/wasm
|
||||
$ emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../../
|
||||
$ make
|
||||
```bash
|
||||
$ mkdir -p out/wasm
|
||||
$ cd out/wasm
|
||||
$ emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../../
|
||||
$ make
|
||||
```
|
||||
|
||||
To use the generated binary, checkout [Kichikuou on Web](https://github.com/kichikuou/web) and copy `out/xsystem35.*` into its `docs` directory.
|
||||
To use the generated binary, check out
|
||||
[Kichikuou on Web](https://github.com/kichikuou/web) and copy `out/xsystem35.*`
|
||||
into its `docs` directory.
|
||||
|
||||
### Android
|
||||
|
||||
|
||||
+80
-39
@@ -1,60 +1,101 @@
|
||||
# xsystem35 for Android
|
||||
|
||||
## Download
|
||||
Prebuilt APKs are [here](https://github.com/kichikuou/xsystem35-sdl2/releases).
|
||||
Minimum supported Android version: 5.0
|
||||
|
||||
## Build
|
||||
Prerequisites:
|
||||
- CMake >=3.13
|
||||
- Android SDK >=28
|
||||
- Android NDK >=r15c
|
||||
## Download
|
||||
You can download prebuilt APKs
|
||||
[here](https://github.com/kichikuou/xsystem35-sdl2/releases).
|
||||
|
||||
### Installing with Obtainium
|
||||
Alternatively, you can install `xsystem35-sdl2` using [Obtainium](https://github.com/ImranR98/Obtainium). Obtainium will detect new releases and allow you to update the app directly.
|
||||
|
||||
1. Install Obtainium on your Android device.
|
||||
2. Click the badge below to add the app to Obtainium:
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/ImranR98/Obtainium/main/assets/graphics/badge_obtainium.png" alt="Get it on Obtainium" height="60">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22io.github.kichikuou.xsystem35%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fkichikuou%2Fxsystem35-sdl2%22%2C%22author%22%3A%22kichikuou%22%2C%22name%22%3A%22xsystem35%22%2C%22categories%22%3A%5B%22games%22%5D%7D)
|
||||
|
||||
## Usage
|
||||
### Basic Usage
|
||||
You can install games either from a prepared ZIP file or directly from a CD-ROM
|
||||
image.
|
||||
|
||||
#### Installing from a ZIP
|
||||
1. Create a ZIP file containing all the game files and BGM files (see
|
||||
[below](#preparing-a-zip) for details), and transfer it to your device.
|
||||
2. Open the app. Tap the option menu (three dots in the top right corner) and
|
||||
select "Install from ZIP".
|
||||
3. Select the ZIP file you created in step 1.
|
||||
4. The game will start.
|
||||
|
||||
#### Installing from a CD image
|
||||
1. Transfer the CD image file(s) to your device.
|
||||
2. Open the app. Tap the option menu and select "Install from CD image".
|
||||
3. Select the image file and, when needed, the matching metadata file at the
|
||||
same time. The supported combinations are:
|
||||
- `.iso`
|
||||
- `.bin` or `.img` with matching `.cue`
|
||||
- `.bin` or `.img` with matching `.ccd`
|
||||
- `.mdf` with matching `.mds`
|
||||
|
||||
In many Android file pickers, tapping a file immediately selects only that
|
||||
one file. Long-press a file first to enter multi-select mode, then select
|
||||
both the image file and its metadata file.
|
||||
4. The game will start.
|
||||
|
||||
### Preparing a ZIP
|
||||
- Include all files from the `GAMEDATA` folder (such as `.ALD` files and
|
||||
others). `.EXE` and `.DLL` files are not necessary, but you can include them
|
||||
if you want.
|
||||
- Music files (`.mp3`, `.ogg`, or `.wav`) whose filenames have a number either
|
||||
at the beginning or just before the extension are recognized as BGM files.
|
||||
For example:
|
||||
- `Track2.mp3`
|
||||
- `15.ogg`
|
||||
- `02 - Title.mp3`
|
||||
- `rance4_03.wav` (Note: The filename shouldn't be `rance403.wav`, as it
|
||||
would be treated as the 403rd track.)
|
||||
|
||||
Note: This ZIP format is also compatible with
|
||||
[Kichikuou on Web](http://kichikuou.github.io/web/).
|
||||
|
||||
### Miscellaneous
|
||||
- To simulate a right-click, tap the black bars on either the left or right,
|
||||
or top or bottom of the screen.
|
||||
- You can export or import save files via the game list's option menu.
|
||||
- To uninstall a game, long-tap its title in the game list.
|
||||
|
||||
## Known Issues
|
||||
- Android versions older than 7.0 cannot handle ZIP files containing Shift-JIS
|
||||
filenames. This issue occurs with some ZIP files distributed on
|
||||
[retroc.net](http://retropc.net/alice/). If you encounter the error message
|
||||
"This type of ZIP is not supported," unzip the file on your PC and re-archive
|
||||
it using modern ZIP creation software.
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Using Android Studio
|
||||
Open this directory as an Android Studio project.
|
||||
|
||||
### Command line build
|
||||
Configure environment variables and run the `gradlew` script in this folder.
|
||||
### Command Line Build
|
||||
Set environment variables and run the `gradlew` script in this directory.
|
||||
|
||||
Example build instructions (for Debian buster):
|
||||
Example build instructions (for Debian bookworm):
|
||||
```sh
|
||||
# Install necessary packages
|
||||
sudo apt install git wget unzip default-jdk-headless cmake ninja-build
|
||||
sudo apt install git wget unzip default-jdk-headless ninja-build
|
||||
|
||||
# Install Android SDK / NDK
|
||||
export ANDROID_SDK_ROOT=$HOME/android-sdk
|
||||
mkdir -p $ANDROID_SDK_ROOT/cmdline-tools
|
||||
wget https://dl.google.com/android/repository/commandlinetools-linux-7302050_latest.zip
|
||||
unzip commandlinetools-linux-7302050_latest.zip -d $ANDROID_SDK_ROOT/cmdline-tools
|
||||
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
|
||||
unzip commandlinetools-linux-11076708_latest.zip -d $ANDROID_SDK_ROOT/cmdline-tools
|
||||
mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/tools
|
||||
yes |$ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager --licenses
|
||||
$ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager ndk-bundle
|
||||
yes | $ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager --licenses
|
||||
$ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager ndk-bundle 'cmake;3.22.1'
|
||||
export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk-bundle
|
||||
|
||||
# Check out and build xsystem35
|
||||
# Clone and build xsystem35
|
||||
git clone https://github.com/kichikuou/xsystem35-sdl2.git
|
||||
cd xsystem35-sdl2/android
|
||||
./gradlew build # or ./gradlew installDebug if you have a connected device
|
||||
```
|
||||
|
||||
## Use
|
||||
### Basic Usage
|
||||
1. Create a ZIP file containing all the game files and BGM files (see [below](#preparing-a-zip) for details), and transfer it to your device.
|
||||
2. Open the app. A list of installed games is displayed. Since nothing has been installed yet, only the "Install from ZIP" button is displayed. Tap it.
|
||||
3. Select the ZIP file you created in 1.
|
||||
4. The game starts. Two-finger touch is treated as a right click.
|
||||
|
||||
### Preparing a ZIP
|
||||
- Include all files in the `GAMEDATA` folder (`.ALD` files and others). `.EXE` and `.DLL` are not really needed, but you can include them as well.
|
||||
- Music files (`.mp3`, `.ogg` or `.wav`) whose file names end with a number are recognized as BGM files. For example:
|
||||
- `Track2.mp3`
|
||||
- `15.ogg`
|
||||
- `rance4_03.wav` (This shouldn't be `rance403.wav`, because it would be treated as the 403rd track)
|
||||
|
||||
Note: This form of ZIP can be used in [Kichikuou on Web](http://kichikuou.github.io/web/) as well.
|
||||
|
||||
### Miscellaneous
|
||||
- You can export / import saved files using the option menu of the game list.
|
||||
- To uninstall a game, long-tap the title in the game list.
|
||||
|
||||
## Known Issues
|
||||
- Android versions older than 7.0 cannot handle ZIPs containing Shift-JIS file names. This is the case with some ZIPs distributed on [retroc.net](http://retropc.net/alice/). If you get the error "This type of ZIP is not supported.", unzip the ZIP file on your PC and re-archive it with a modern ZIP creation software.
|
||||
|
||||
+31
-17
@@ -9,33 +9,47 @@ else {
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
if (buildAsApplication) {
|
||||
namespace "io.github.kichikuou.xsystem35"
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
if (buildAsApplication) {
|
||||
applicationId "io.github.kichikuou.xsystem35"
|
||||
}
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 5
|
||||
versionName "0.2.3"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 36
|
||||
versionName "2.20.0" + gitRevision()
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-16", "-DANDROID_STL=c++_static"
|
||||
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
|
||||
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile rootProject.file('keystore.jks')
|
||||
storePassword System.getenv('KEYSTORE_PASSWORD')
|
||||
keyAlias System.getenv('KEY_ALIAS')
|
||||
keyPassword System.getenv('KEY_PASSWORD')
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
if (rootProject.file('keystore.jks').exists()) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
applicationVariants.all { variant ->
|
||||
tasks["merge${variant.name.capitalize()}Assets"]
|
||||
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
|
||||
}
|
||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'libs'
|
||||
assets.srcDir '../../fonts'
|
||||
}
|
||||
aaptOptions {
|
||||
// Disable asset compression for fonts.
|
||||
@@ -44,18 +58,16 @@ android {
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
// xsystem35 requires CMake >=3.13, so SDK-bundled CMake
|
||||
// (3.6 or 3.10.2) cannot be used.
|
||||
version getInstalledCmakeVersion()
|
||||
version '3.22.1'
|
||||
path 'jni/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
lintOptions {
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
|
||||
if (buildAsLibrary) {
|
||||
libraryVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
@@ -72,8 +84,10 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
}
|
||||
|
||||
static String getInstalledCmakeVersion() {
|
||||
('cmake --version'.execute().text =~ /cmake version ([0-9.]+)/)[0][1]
|
||||
static String gitRevision() {
|
||||
' (git ' + "git rev-parse --short HEAD".execute().text.trim() + ')'
|
||||
}
|
||||
|
||||
@@ -1,49 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(GAME)
|
||||
set(PROJECT_ROOT_DIR ../../..)
|
||||
|
||||
# armeabi-v7a requires cpufeatures library
|
||||
# include(AndroidNdkModules)
|
||||
# android_ndk_import_module_cpufeatures()
|
||||
|
||||
# Compilation of SDL and companion libraries
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
SDL
|
||||
URL https://libsdl.org/release/SDL2-2.0.16.tar.gz
|
||||
URL_HASH SHA1=57825428174adb2ac947e4014080c262505aa972
|
||||
PATCH_COMMAND patch -p1 -i ${CMAKE_CURRENT_LIST_DIR}/SDL.patch
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
|
||||
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
|
||||
)
|
||||
# TODO: Use SDL2_ttf 2.0.16 once released
|
||||
FetchContent_Declare(
|
||||
SDL_ttf
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
|
||||
GIT_TAG 11722a2bb4335d489d724c08cfdbb45f97d07aa7
|
||||
freetype
|
||||
URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz
|
||||
https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz
|
||||
URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678
|
||||
)
|
||||
# TODO: Use SDL2_mixer 2.0.5 once released
|
||||
FetchContent_Declare(
|
||||
SDL_mixer
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL_mixer.git
|
||||
GIT_TAG 20433729fb318f7b6f773a42ffa410114cb067f2
|
||||
URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.8.0/SDL2_mixer-2.8.0.tar.gz
|
||||
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
|
||||
)
|
||||
|
||||
# Compilation of SDL and companion libraries
|
||||
FetchContent_GetProperties(SDL)
|
||||
if(NOT sdl_POPULATED)
|
||||
FetchContent_Populate(SDL)
|
||||
add_subdirectory(${sdl_SOURCE_DIR} ${sdl_BINARY_DIR})
|
||||
endif()
|
||||
FetchContent_GetProperties(SDL_ttf)
|
||||
if(NOT sdl_ttf_POPULATED)
|
||||
FetchContent_Populate(SDL_ttf)
|
||||
add_subdirectory(${sdl_ttf_SOURCE_DIR} ${sdl_ttf_BINARY_DIR})
|
||||
endif()
|
||||
FetchContent_GetProperties(SDL_mixer)
|
||||
if(NOT sdl_mixer_POPULATED)
|
||||
FetchContent_Populate(SDL_mixer)
|
||||
SET(SUPPORT_OGG ON CACHE BOOL "Enable OGG support in SDL_mixer" FORCE)
|
||||
add_subdirectory(${sdl_mixer_SOURCE_DIR} ${sdl_mixer_BINARY_DIR})
|
||||
target_compile_definitions(SDL2_mixer PRIVATE MUSIC_WAV)
|
||||
endif()
|
||||
# Only FreeType's own rasterizer is needed, none of its optional dependencies.
|
||||
set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE)
|
||||
set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE)
|
||||
set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE)
|
||||
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE)
|
||||
set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE)
|
||||
|
||||
set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
|
||||
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
|
||||
set(SDL2MIXER_MOD OFF CACHE BOOL "Support loading MOD music" FORCE)
|
||||
set(SDL2MIXER_MIDI OFF CACHE BOOL "Enable MIDI music" FORCE)
|
||||
set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE)
|
||||
set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE)
|
||||
set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(SDL freetype SDL_mixer)
|
||||
|
||||
# The main CMakeLists.txt of xsystem35
|
||||
add_subdirectory(../../.. xsystem35)
|
||||
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
|
||||
|
||||
# Copy asset files
|
||||
set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/main/assets)
|
||||
file(MAKE_DIRECTORY ${ASSETS_DIR}/licenses)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/fonts/MTLc3m.ttf ${ASSETS_DIR}/MTLc3m.ttf)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/fonts/mincho.ttf ${ASSETS_DIR}/mincho.ttf)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/COPYING ${ASSETS_DIR}/licenses/xsystem35)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/MTLc3m.txt ${ASSETS_DIR}/licenses/MTLc3m)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mincho)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui)
|
||||
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
|
||||
file(COPY_FILE ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index e7ab00feb..d86f0087a 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -2053,6 +2053,12 @@ if(SDL_SHARED)
|
||||
set_property(TARGET SDL2 APPEND_STRING PROPERTY COMPILE_FLAGS "-fobjc-arc")
|
||||
target_compile_definitions(SDL2 PRIVATE IOS_DYLIB=1)
|
||||
endif()
|
||||
+ if (ANDROID)
|
||||
+ # Link the cpufeatures library in Android NDK.
|
||||
+ # Since we don't use a SDK-bundled CMAKE, AndroidNdkModules module cannot be used.
|
||||
+ target_include_directories(SDL2 PRIVATE ${ANDROID_NDK}/sources/android/cpufeatures)
|
||||
+ target_sources(SDL2 PRIVATE ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
|
||||
+ endif()
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
@@ -13,6 +13,6 @@ LOCAL_SRC_FILES := YourSourceHere.c
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := SDL2
|
||||
|
||||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog
|
||||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
Vendored
+81
@@ -15,3 +15,84 @@
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
|
||||
void nativeCommitText(java.lang.String, int);
|
||||
void nativeGenerateScancodeForUnichar(char);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
|
||||
# for some reason these aren't compatible with allowoptimization modifier
|
||||
boolean supportsRelativeMouse();
|
||||
void setWindowStyle(boolean);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
|
||||
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
|
||||
boolean onNativeSoftReturnKey();
|
||||
void onNativeKeyboardFocusLost();
|
||||
boolean isScreenKeyboardShown();
|
||||
android.util.DisplayMetrics getDisplayDPI();
|
||||
java.lang.String clipboardGetText();
|
||||
boolean clipboardHasText();
|
||||
void clipboardSetText(java.lang.String);
|
||||
int createCustomCursor(int[], int, int, int, int);
|
||||
void destroyCustomCursor(int);
|
||||
android.content.Context getContext();
|
||||
boolean getManifestEnvironmentVariables();
|
||||
android.view.Surface getNativeSurface();
|
||||
void initTouch();
|
||||
boolean isAndroidTV();
|
||||
boolean isChromebook();
|
||||
boolean isDeXMode();
|
||||
boolean isTablet();
|
||||
void manualBackButton();
|
||||
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
|
||||
void minimizeWindow();
|
||||
int openURL(java.lang.String);
|
||||
void requestPermission(java.lang.String, int);
|
||||
int showToast(java.lang.String, int, int, int, int);
|
||||
boolean sendMessage(int, int);
|
||||
boolean setActivityTitle(java.lang.String);
|
||||
boolean setCustomCursor(int);
|
||||
void setOrientation(int, int, boolean, java.lang.String);
|
||||
boolean setRelativeMouseEnabled(boolean);
|
||||
boolean setSystemCursor(int);
|
||||
boolean shouldMinimizeOnFocusLoss();
|
||||
boolean showTextInput(int, int, int, int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
|
||||
boolean initialize(boolean, boolean);
|
||||
boolean openDevice(int);
|
||||
int sendOutputReport(int, byte[]);
|
||||
int sendFeatureReport(int, byte[]);
|
||||
boolean getFeatureReport(int, byte[]);
|
||||
void closeDevice(int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
|
||||
int[] getAudioOutputDevices();
|
||||
int[] getAudioInputDevices();
|
||||
int[] audioOpen(int, int, int, int, int);
|
||||
void audioWriteFloatBuffer(float[]);
|
||||
void audioWriteShortBuffer(short[]);
|
||||
void audioWriteByteBuffer(byte[]);
|
||||
void audioClose();
|
||||
int[] captureOpen(int, int, int, int, int);
|
||||
int captureReadFloatBuffer(float[], boolean);
|
||||
int captureReadShortBuffer(short[], boolean);
|
||||
int captureReadByteBuffer(byte[], boolean);
|
||||
void captureClose();
|
||||
void audioSetThreadPriority(boolean, int);
|
||||
native int nativeSetupJNI();
|
||||
native void removeAudioDevice(boolean, int);
|
||||
native void addAudioDevice(boolean, int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
|
||||
void pollInputDevices();
|
||||
void pollHapticDevices();
|
||||
void hapticRun(int, float, int);
|
||||
void hapticStop(int);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="io.github.kichikuou.xsystem35"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
@@ -37,10 +36,14 @@
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" /> -->
|
||||
|
||||
<!-- Allow writing to external storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
|
||||
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" /> -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
|
||||
|
||||
<!-- Allow access to the vibrator -->
|
||||
<!-- <uses-permission android:name="android.permission.VIBRATE" /> -->
|
||||
|
||||
@@ -63,8 +66,11 @@
|
||||
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
||||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
||||
-->
|
||||
<meta-data android:name="SDL_ENV.SDL_IOS_ORIENTATIONS" android:value="LandscapeLeft LandscapeRight"/>
|
||||
|
||||
<activity android:name=".LauncherActivity"
|
||||
android:exported="true">
|
||||
|
||||
<activity android:name=".LauncherActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -73,9 +79,10 @@
|
||||
|
||||
<activity android:name=".GameActivity"
|
||||
android:label="@string/app_name"
|
||||
android:process=":game"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:theme="@style/AppTheme"
|
||||
android:hardwareAccelerated="true"
|
||||
>
|
||||
|
||||
@@ -88,6 +95,17 @@
|
||||
</intent-filter>
|
||||
-->
|
||||
</activity>
|
||||
|
||||
<activity android:name=".LicensesMenuActivity"
|
||||
android:label="@string/action_licenses"
|
||||
android:parentActivityName=".LauncherActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".LicensesActivity"
|
||||
android:parentActivityName=".LicensesMenuActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
/* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.os.ParcelFileDescriptor
|
||||
import java.io.Closeable
|
||||
import java.io.EOFException
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.util.Locale
|
||||
|
||||
internal class CdImageReader private constructor(
|
||||
private val image: RandomAccessImage,
|
||||
private val tracks: List<TrackInfo?>,
|
||||
) : Closeable {
|
||||
fun readSector(sector: Int, buffer: ByteArray) {
|
||||
readDataFully(sector.toLong() * ISO_SECTOR_SIZE, buffer, 0, ISO_SECTOR_SIZE)
|
||||
}
|
||||
|
||||
fun readDataFully(offset: Long, buffer: ByteArray, bufferOffset: Int, length: Int) {
|
||||
// ISO9660 always sees logical 2048-byte data sectors even when the
|
||||
// image stores each sector as a raw 2352-byte CD-ROM block.
|
||||
var logicalOffset = offset
|
||||
var outOffset = bufferOffset
|
||||
var remaining = length
|
||||
val sectorBuffer = ByteArray(ISO_SECTOR_SIZE)
|
||||
while (remaining > 0) {
|
||||
val sector = (logicalOffset / ISO_SECTOR_SIZE).toInt()
|
||||
val offsetInSector = (logicalOffset % ISO_SECTOR_SIZE).toInt()
|
||||
val count = minOf(remaining, ISO_SECTOR_SIZE - offsetInSector)
|
||||
readSectorPayload(sector, sectorBuffer)
|
||||
sectorBuffer.copyInto(buffer, outOffset, offsetInSector, offsetInSector + count)
|
||||
logicalOffset += count
|
||||
outOffset += count
|
||||
remaining -= count
|
||||
}
|
||||
}
|
||||
|
||||
fun audioTracks(): List<Int> {
|
||||
return tracks.mapIndexedNotNull { trackNumber, track ->
|
||||
if (track?.isAudio == true) trackNumber else null
|
||||
}
|
||||
}
|
||||
|
||||
fun extractAudioTrack(trackNumber: Int, output: OutputStream) {
|
||||
val track = tracks.getOrNull(trackNumber)
|
||||
if (track?.isAudio != true) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
val dataSize = track.numSectors.toLong() * RAW_AUDIO_SECTOR_SIZE
|
||||
// CD-DA sectors are already 44.1kHz stereo 16-bit PCM; wrapping the
|
||||
// raw sector stream in a WAV header makes it playable by SDL_mixer.
|
||||
writeWaveHeader(output, dataSize)
|
||||
val buffer = ByteArray(track.blockSize * 16)
|
||||
var sector = 0
|
||||
while (sector < track.numSectors) {
|
||||
val sectorsToRead = minOf(16, track.numSectors - sector)
|
||||
val bytesToRead = sectorsToRead * track.blockSize
|
||||
image.readFully(track.offset + sector.toLong() * track.blockSize, buffer, 0, bytesToRead)
|
||||
output.write(buffer, 0, sectorsToRead * RAW_AUDIO_SECTOR_SIZE)
|
||||
sector += sectorsToRead
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
image.close()
|
||||
}
|
||||
|
||||
private fun readSectorPayload(sector: Int, buffer: ByteArray) {
|
||||
val track = findDataTrack(sector)
|
||||
val offset = track.offset + (sector - track.startSector).toLong() * track.blockSize + track.blockOffset
|
||||
image.readFully(offset, buffer, 0, ISO_SECTOR_SIZE)
|
||||
}
|
||||
|
||||
private fun findDataTrack(sector: Int): TrackInfo {
|
||||
for (track in tracks) {
|
||||
if (track == null || track.isAudio) {
|
||||
continue
|
||||
}
|
||||
if (sector >= track.startSector && sector < track.startSector + track.numSectors) {
|
||||
return track
|
||||
}
|
||||
}
|
||||
throw InstallFailureException(R.string.invalid_iso9660_image)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ISO_SECTOR_SIZE = 2048
|
||||
private const val RAW_AUDIO_SECTOR_SIZE = 2352
|
||||
private const val WAVE_HEADER_SIZE = 44
|
||||
private const val MDS_MODE_AUDIO = 0xa9
|
||||
private const val MDS_MODE_MODE1 = 0xaa
|
||||
|
||||
fun open(
|
||||
contentResolver: ContentResolver,
|
||||
files: List<SelectedInstallFile>,
|
||||
tempDir: File,
|
||||
): CdImageReader {
|
||||
val selected = selectImageFiles(files)
|
||||
val image = RandomAccessImage.open(contentResolver, selected.image, tempDir)
|
||||
try {
|
||||
if (selected.metadata == null) {
|
||||
return CdImageReader(image, listOf(null, TrackInfo.iso(image.size)))
|
||||
}
|
||||
val metadataName = selected.metadata.displayName.lowercase(Locale.US)
|
||||
val tracks = when {
|
||||
metadataName.endsWith(".cue") -> {
|
||||
val cueText = readMetadataText(contentResolver, selected.metadata)
|
||||
parseCue(cueText, image.size)
|
||||
}
|
||||
metadataName.endsWith(".ccd") -> {
|
||||
val ccdText = readMetadataText(contentResolver, selected.metadata)
|
||||
parseCcd(ccdText, image.size)
|
||||
}
|
||||
metadataName.endsWith(".mds") -> {
|
||||
val mdsBytes = readMetadataBytes(contentResolver, selected.metadata)
|
||||
parseMds(mdsBytes)
|
||||
}
|
||||
else -> throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
return CdImageReader(image, tracks)
|
||||
} catch (e: Exception) {
|
||||
image.close()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
internal fun openForFile(
|
||||
imageFile: File,
|
||||
metadataName: String? = null,
|
||||
metadataBytes: ByteArray? = null,
|
||||
): CdImageReader {
|
||||
val image = RandomAccessImage.open(imageFile)
|
||||
try {
|
||||
if (metadataName == null) {
|
||||
return CdImageReader(image, listOf(null, TrackInfo.iso(image.size)))
|
||||
}
|
||||
val tracks = when {
|
||||
metadataName.lowercase(Locale.US).endsWith(".cue") -> {
|
||||
parseCue(metadataBytes?.toString(Charsets.UTF_8) ?: "", image.size)
|
||||
}
|
||||
metadataName.lowercase(Locale.US).endsWith(".ccd") -> {
|
||||
parseCcd(metadataBytes?.toString(Charsets.UTF_8) ?: "", image.size)
|
||||
}
|
||||
metadataName.lowercase(Locale.US).endsWith(".mds") -> {
|
||||
parseMds(metadataBytes ?: ByteArray(0))
|
||||
}
|
||||
else -> throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
return CdImageReader(image, tracks)
|
||||
} catch (e: Exception) {
|
||||
image.close()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectImageFiles(files: List<SelectedInstallFile>): SelectedImageFiles {
|
||||
if (files.isEmpty()) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
val isoFiles = files.filter { it.displayName.lowercase(Locale.US).endsWith(".iso") }
|
||||
if (isoFiles.size == 1 && files.size == 1) {
|
||||
return SelectedImageFiles(isoFiles.single(), null)
|
||||
}
|
||||
|
||||
val imageFiles = files.filter {
|
||||
val name = it.displayName.lowercase(Locale.US)
|
||||
name.endsWith(".bin") || name.endsWith(".img") || name.endsWith(".mdf")
|
||||
}
|
||||
val metadataFiles = files.filter {
|
||||
val name = it.displayName.lowercase(Locale.US)
|
||||
name.endsWith(".cue") || name.endsWith(".ccd") || name.endsWith(".mds")
|
||||
}
|
||||
if (imageFiles.size == 1 && metadataFiles.isEmpty() && files.size == 1 ||
|
||||
imageFiles.isEmpty() && metadataFiles.size == 1 && files.size == 1
|
||||
) {
|
||||
throw InstallFailureException(R.string.missing_cd_image_metadata)
|
||||
}
|
||||
if (imageFiles.size != 1 || metadataFiles.size != 1 || files.size != 2) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
val imageBase = imageFiles.single().baseName()
|
||||
val metadataBase = metadataFiles.single().baseName()
|
||||
if (imageBase != metadataBase) {
|
||||
throw InstallFailureException(R.string.missing_cd_image_metadata)
|
||||
}
|
||||
if (!isSupportedMetadataForImage(imageFiles.single(), metadataFiles.single())) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
return SelectedImageFiles(imageFiles.single(), metadataFiles.single())
|
||||
}
|
||||
|
||||
private fun isSupportedMetadataForImage(
|
||||
image: SelectedInstallFile,
|
||||
metadata: SelectedInstallFile,
|
||||
): Boolean {
|
||||
val imageName = image.displayName.lowercase(Locale.US)
|
||||
val metadataName = metadata.displayName.lowercase(Locale.US)
|
||||
return if (imageName.endsWith(".mdf")) {
|
||||
metadataName.endsWith(".mds")
|
||||
} else {
|
||||
metadataName.endsWith(".cue") || metadataName.endsWith(".ccd")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readMetadataText(
|
||||
contentResolver: ContentResolver,
|
||||
file: SelectedInstallFile,
|
||||
): String {
|
||||
return contentResolver.openInputStream(file.uri)?.bufferedReader()?.use {
|
||||
it.readText()
|
||||
} ?: throw InstallFailureException(R.string.cd_image_read_error)
|
||||
}
|
||||
|
||||
private fun readMetadataBytes(
|
||||
contentResolver: ContentResolver,
|
||||
file: SelectedInstallFile,
|
||||
): ByteArray {
|
||||
return contentResolver.openInputStream(file.uri)?.use {
|
||||
it.readBytes()
|
||||
} ?: throw InstallFailureException(R.string.cd_image_read_error)
|
||||
}
|
||||
|
||||
private fun parseCue(cueText: String, imageSize: Long): List<TrackInfo?> {
|
||||
val cueTracks = mutableListOf<CueTrack?>()
|
||||
var currentTrack: Int? = null
|
||||
for (line in cueText.lines()) {
|
||||
val fields = line.trim().split(Regex("\\s+"))
|
||||
if (fields.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
when (fields[0].uppercase(Locale.US)) {
|
||||
"TRACK" -> {
|
||||
if (fields.size < 3) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
currentTrack = fields[1].toIntOrNull()
|
||||
?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
while (cueTracks.size <= currentTrack) {
|
||||
cueTracks.add(null)
|
||||
}
|
||||
cueTracks[currentTrack] = when (fields[2].uppercase(Locale.US)) {
|
||||
"MODE1/2048" -> CueTrack(false, 2048, 0)
|
||||
"MODE1/2352" -> CueTrack(false, 2352, 16)
|
||||
"AUDIO" -> CueTrack(true, 2352, 0)
|
||||
else -> throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
}
|
||||
"INDEX" -> {
|
||||
val trackNumber = currentTrack ?: continue
|
||||
if (fields.size < 3) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
val indexNumber = fields[1].toIntOrNull()
|
||||
?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
cueTracks[trackNumber]?.index?.put(indexNumber, indexToSector(fields[2]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeTrackInfo(cueTracks, imageSize)
|
||||
}
|
||||
|
||||
private fun parseCcd(ccdText: String, imageSize: Long): List<TrackInfo?> {
|
||||
val cueTracks = mutableListOf<CueTrack?>()
|
||||
var currentTrack: Int? = null
|
||||
for (line in ccdText.lines()) {
|
||||
val trimmed = line.trim()
|
||||
val trackMatch = Regex("""\[TRACK ([0-9]+)]""").matchEntire(trimmed)
|
||||
if (trackMatch != null) {
|
||||
currentTrack = trackMatch.groupValues[1].toInt()
|
||||
while (cueTracks.size <= currentTrack) {
|
||||
cueTracks.add(null)
|
||||
}
|
||||
cueTracks[currentTrack] = CueTrack(false, 2352, 16)
|
||||
continue
|
||||
}
|
||||
val trackNumber = currentTrack ?: continue
|
||||
val keyValue = trimmed.split("=", limit = 2)
|
||||
if (keyValue.size != 2) {
|
||||
continue
|
||||
}
|
||||
when (keyValue[0].uppercase(Locale.US)) {
|
||||
"MODE" -> if (keyValue[1] == "0") {
|
||||
cueTracks[trackNumber] = CueTrack(true, 2352, 0, cueTracks[trackNumber]?.index ?: mutableMapOf())
|
||||
}
|
||||
"INDEX 0" -> cueTracks[trackNumber]?.index?.put(
|
||||
0,
|
||||
keyValue[1].toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
)
|
||||
"INDEX 1" -> cueTracks[trackNumber]?.index?.put(
|
||||
1,
|
||||
keyValue[1].toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
)
|
||||
}
|
||||
}
|
||||
return makeTrackInfo(cueTracks, imageSize)
|
||||
}
|
||||
|
||||
private fun parseMds(mdsBytes: ByteArray): List<TrackInfo?> {
|
||||
if (mdsBytes.size < 0x70 ||
|
||||
String(mdsBytes, 0, 16, Charsets.US_ASCII).trimEnd('\u0000') != "MEDIA DESCRIPTOR"
|
||||
) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
val entries = mdsBytes[0x62].toInt() and 0xff
|
||||
if (0x70 + entries * 0x58 > mdsBytes.size) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
|
||||
val tracks = MutableList<TrackInfo?>(100) { null }
|
||||
for (i in 0 until entries) {
|
||||
val trackOffset = 0x70 + i * 0x50
|
||||
val extraOffset = 0x70 + entries * 0x50 + i * 8
|
||||
val mode = mdsBytes[trackOffset].toInt() and 0xff
|
||||
val trackNumber = mdsBytes[trackOffset + 0x04].toInt() and 0xff
|
||||
val sectorSize = readLittleEndianShort(mdsBytes, trackOffset + 0x10)
|
||||
// MDS offsets are treated as 32-bit file offsets, so images
|
||||
// beyond 4GB are unsupported.
|
||||
val imageOffset = readLittleEndianInt(mdsBytes, trackOffset + 0x28)
|
||||
val sectors = readLittleEndianInt(mdsBytes, extraOffset + 0x04).toInt()
|
||||
if (trackNumber >= tracks.size) {
|
||||
continue
|
||||
}
|
||||
tracks[trackNumber] = when (mode) {
|
||||
MDS_MODE_AUDIO -> TrackInfo(
|
||||
isAudio = true,
|
||||
offset = imageOffset,
|
||||
blockSize = sectorSize,
|
||||
blockOffset = 0,
|
||||
startSector = 0,
|
||||
numSectors = sectors,
|
||||
)
|
||||
MDS_MODE_MODE1 -> TrackInfo(
|
||||
isAudio = false,
|
||||
offset = imageOffset,
|
||||
blockSize = sectorSize,
|
||||
blockOffset = 16,
|
||||
startSector = 0,
|
||||
numSectors = sectors,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (tracks.getOrNull(1)?.isAudio != false) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
return tracks
|
||||
}
|
||||
|
||||
private fun makeTrackInfo(cueTracks: List<CueTrack?>, imageSize: Long): List<TrackInfo?> {
|
||||
val tracks = MutableList<TrackInfo?>(cueTracks.size) { null }
|
||||
var offset = 0L
|
||||
var startSector = 0
|
||||
for (trackNumber in 1 until cueTracks.size) {
|
||||
val cueTrack = cueTracks[trackNumber] ?: continue
|
||||
val index1 = cueTrack.index[1]
|
||||
?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
// INDEX 0 describes a pregap, but a zero INDEX 0 is treated as absent.
|
||||
cueTrack.index[0]?.takeIf { it != 0 }?.let { index0 ->
|
||||
val gap = index1 - index0
|
||||
if (gap > 0) {
|
||||
startSector += gap
|
||||
offset += gap.toLong() * cueTrack.blockSize
|
||||
}
|
||||
}
|
||||
val nextTrack = cueTracks.drop(trackNumber + 1).firstOrNull { it != null }
|
||||
val nextStart = nextTrack?.let { it.index[0]?.takeIf { index -> index != 0 } ?: it.index[1] }
|
||||
val numSectors = if (nextStart != null) {
|
||||
nextStart - index1
|
||||
} else {
|
||||
((imageSize - offset) / cueTrack.blockSize).toInt()
|
||||
}
|
||||
if (numSectors <= 0) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
tracks[trackNumber] = TrackInfo(
|
||||
isAudio = cueTrack.isAudio,
|
||||
offset = offset,
|
||||
blockSize = cueTrack.blockSize,
|
||||
blockOffset = cueTrack.blockOffset,
|
||||
startSector = startSector,
|
||||
numSectors = numSectors,
|
||||
)
|
||||
startSector += numSectors
|
||||
offset += numSectors.toLong() * cueTrack.blockSize
|
||||
}
|
||||
return tracks
|
||||
}
|
||||
|
||||
private fun indexToSector(index: String): Int {
|
||||
val parts = index.split(":").map {
|
||||
it.toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
if (parts.size != 3) {
|
||||
throw InstallFailureException(R.string.unsupported_cd_image)
|
||||
}
|
||||
return parts[0] * 60 * 75 + parts[1] * 75 + parts[2]
|
||||
}
|
||||
|
||||
private fun writeWaveHeader(output: OutputStream, dataSize: Long) {
|
||||
val header = ByteArray(WAVE_HEADER_SIZE)
|
||||
writeAscii(header, 0, "RIFF")
|
||||
writeLittleEndianInt(header, 4, dataSize + 36)
|
||||
writeAscii(header, 8, "WAVE")
|
||||
writeAscii(header, 12, "fmt ")
|
||||
writeLittleEndianInt(header, 16, 16)
|
||||
writeLittleEndianShort(header, 20, 1)
|
||||
writeLittleEndianShort(header, 22, 2)
|
||||
writeLittleEndianInt(header, 24, 44100)
|
||||
writeLittleEndianInt(header, 28, 44100 * 2 * 2)
|
||||
writeLittleEndianShort(header, 32, 2 * 2)
|
||||
writeLittleEndianShort(header, 34, 16)
|
||||
writeAscii(header, 36, "data")
|
||||
writeLittleEndianInt(header, 40, dataSize)
|
||||
output.write(header)
|
||||
}
|
||||
|
||||
private fun writeAscii(buffer: ByteArray, offset: Int, value: String) {
|
||||
value.toByteArray(Charsets.US_ASCII).copyInto(buffer, offset)
|
||||
}
|
||||
|
||||
private fun writeLittleEndianShort(buffer: ByteArray, offset: Int, value: Int) {
|
||||
buffer[offset] = value.toByte()
|
||||
buffer[offset + 1] = (value shr 8).toByte()
|
||||
}
|
||||
|
||||
private fun writeLittleEndianInt(buffer: ByteArray, offset: Int, value: Long) {
|
||||
buffer[offset] = value.toByte()
|
||||
buffer[offset + 1] = (value shr 8).toByte()
|
||||
buffer[offset + 2] = (value shr 16).toByte()
|
||||
buffer[offset + 3] = (value shr 24).toByte()
|
||||
}
|
||||
|
||||
private fun readLittleEndianShort(buffer: ByteArray, offset: Int): Int {
|
||||
return (buffer[offset].toInt() and 0xff) or
|
||||
((buffer[offset + 1].toInt() and 0xff) shl 8)
|
||||
}
|
||||
|
||||
private fun readLittleEndianInt(buffer: ByteArray, offset: Int): Long {
|
||||
return (buffer[offset].toLong() and 0xff) or
|
||||
((buffer[offset + 1].toLong() and 0xff) shl 8) or
|
||||
((buffer[offset + 2].toLong() and 0xff) shl 16) or
|
||||
((buffer[offset + 3].toLong() and 0xff) shl 24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class SelectedImageFiles(
|
||||
val image: SelectedInstallFile,
|
||||
val metadata: SelectedInstallFile?,
|
||||
)
|
||||
|
||||
private data class CueTrack(
|
||||
val isAudio: Boolean,
|
||||
val blockSize: Int,
|
||||
val blockOffset: Int,
|
||||
val index: MutableMap<Int, Int> = mutableMapOf(),
|
||||
)
|
||||
|
||||
private data class TrackInfo(
|
||||
val isAudio: Boolean,
|
||||
val offset: Long,
|
||||
val blockSize: Int,
|
||||
val blockOffset: Int,
|
||||
val startSector: Int,
|
||||
val numSectors: Int,
|
||||
) {
|
||||
companion object {
|
||||
fun iso(imageSize: Long): TrackInfo {
|
||||
return TrackInfo(
|
||||
isAudio = false,
|
||||
offset = 0,
|
||||
blockSize = CdImageReader.ISO_SECTOR_SIZE,
|
||||
blockOffset = 0,
|
||||
startSector = 0,
|
||||
numSectors = (imageSize / CdImageReader.ISO_SECTOR_SIZE).toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RandomAccessImage private constructor(
|
||||
private val channel: FileChannel,
|
||||
private val descriptor: ParcelFileDescriptor?,
|
||||
private val tempFile: File?,
|
||||
) : Closeable {
|
||||
val size: Long = channel.size()
|
||||
|
||||
fun readFully(offset: Long, buffer: ByteArray, bufferOffset: Int, length: Int) {
|
||||
val byteBuffer = ByteBuffer.wrap(buffer, bufferOffset, length)
|
||||
var pos = offset
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
val read = channel.read(byteBuffer, pos)
|
||||
if (read < 0) {
|
||||
throw EOFException("Unexpected end of CD image")
|
||||
}
|
||||
pos += read
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
channel.close()
|
||||
descriptor?.close()
|
||||
tempFile?.delete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun open(file: File): RandomAccessImage {
|
||||
return RandomAccessImage(FileInputStream(file).channel, null, null)
|
||||
}
|
||||
|
||||
fun open(
|
||||
contentResolver: ContentResolver,
|
||||
file: SelectedInstallFile,
|
||||
tempDir: File,
|
||||
): RandomAccessImage {
|
||||
val descriptor = contentResolver.openFileDescriptor(file.uri, "r")
|
||||
?: throw InstallFailureException(R.string.cd_image_read_error)
|
||||
try {
|
||||
val channel = FileInputStream(descriptor.fileDescriptor).channel
|
||||
channel.size()
|
||||
return RandomAccessImage(channel, descriptor, null)
|
||||
} catch (e: Exception) {
|
||||
descriptor.close()
|
||||
}
|
||||
|
||||
// Some SAF providers expose the URI as a pipe. Copy those to an
|
||||
// app-private temp file so the CD reader can use positional reads.
|
||||
val tempFile = File.createTempFile("cdimage-", ".img", tempDir)
|
||||
try {
|
||||
contentResolver.openInputStream(file.uri)?.use { input ->
|
||||
tempFile.outputStream().buffered().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
} ?: throw InstallFailureException(R.string.cd_image_read_error)
|
||||
return RandomAccessImage(FileInputStream(tempFile).channel, null, tempFile)
|
||||
} catch (e: Exception) {
|
||||
tempFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SelectedInstallFile.baseName(): String {
|
||||
return displayName.substringBeforeLast('.').lowercase(Locale.US)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package io.github.kichikuou.xsystem35
|
||||
import android.app.AlertDialog
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.widget.EditText
|
||||
@@ -30,40 +31,54 @@ import java.io.IOException
|
||||
|
||||
// Intent for this activity must have the following extra:
|
||||
// - EXTRA_GAME_ROOT (string): A path to the game installation.
|
||||
// - EXTRA_SAVE_DIRECTORY (string): A path to the save data directory.
|
||||
class GameActivity : SDLActivity() {
|
||||
companion object {
|
||||
const val EXTRA_GAME_ROOT = "GAME_ROOT"
|
||||
const val EXTRA_SAVE_DIRECTORY = "SAVE_DIRECTORY"
|
||||
const val EXTRA_ARCHIVE_NAME = "ARCHIVE_NAME"
|
||||
}
|
||||
|
||||
private lateinit var gameRoot: File
|
||||
private lateinit var cdda: CddaPlayer
|
||||
private val midi = MidiPlayer()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT))
|
||||
cdda = CddaPlayer(File(gameRoot, Launcher.PLAYLIST_FILE))
|
||||
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT)!!)
|
||||
// Workaround for https://github.com/libsdl-org/SDL/issues/8995
|
||||
SDLActivity.setWindowStyle(true)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
cdda.onActivityStop()
|
||||
midi.onActivityStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
cdda.onActivityResume()
|
||||
midi.onActivityResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
try {
|
||||
super.onDestroy()
|
||||
} finally {
|
||||
try {
|
||||
midi.release()
|
||||
} finally {
|
||||
Process.killProcess(Process.myPid())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> {
|
||||
return arrayOf("hidapi", "SDL2", "xsystem35")
|
||||
return arrayOf("SDL2", "xsystem35")
|
||||
}
|
||||
|
||||
override fun getArguments(): Array<String> {
|
||||
return arrayOf("-gamedir", intent.getStringExtra(EXTRA_GAME_ROOT))
|
||||
return arrayOf(
|
||||
"-gamedir", intent.getStringExtra(EXTRA_GAME_ROOT)!!,
|
||||
"-savedir", intent.getStringExtra(EXTRA_SAVE_DIRECTORY)!!)
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
@@ -75,7 +90,6 @@ class GameActivity : SDLActivity() {
|
||||
return
|
||||
}
|
||||
File(gameRoot, Launcher.TITLE_FILE).writeText(str)
|
||||
Launcher.updateGameList()
|
||||
}
|
||||
|
||||
private fun textInputDialog(msg: String, oldVal: String, maxLen: Int, result: Array<String?>) {
|
||||
@@ -120,9 +134,6 @@ class GameActivity : SDLActivity() {
|
||||
}
|
||||
|
||||
// The functions below are called in the SDL thread by JNI.
|
||||
@Suppress("unused") fun cddaStart(track: Int, loop: Boolean) = cdda.start(track, loop)
|
||||
@Suppress("unused") fun cddaStop() = cdda.stop()
|
||||
@Suppress("unused") fun cddaCurrentPosition() = cdda.currentPosition()
|
||||
@Suppress("unused") fun midiStart(path: String, loop: Boolean) = midi.start(path, loop)
|
||||
@Suppress("unused") fun midiStop() = midi.stop()
|
||||
@Suppress("unused") fun midiCurrentPosition() = midi.currentPosition()
|
||||
@@ -156,75 +167,22 @@ class GameActivity : SDLActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private class CddaPlayer(private val playlistPath: File) {
|
||||
private val playlist =
|
||||
try {
|
||||
playlistPath.readLines()
|
||||
} catch (e: IOException) {
|
||||
Log.e("loadPlaylist", "Cannot load $playlistPath", e)
|
||||
emptyList<String>()
|
||||
}
|
||||
private var currentTrack = 0
|
||||
private val player = MediaPlayer()
|
||||
private var playerPaused = false
|
||||
|
||||
fun start(track: Int, loop: Boolean) {
|
||||
val f = playlist.elementAtOrNull(track)
|
||||
if (f.isNullOrEmpty()) {
|
||||
Log.w("cddaStart", "No playlist entry for track $track")
|
||||
return
|
||||
}
|
||||
Log.v("cddaStart", "$f Loop:$loop")
|
||||
try {
|
||||
player.apply {
|
||||
reset()
|
||||
setDataSource(File(playlistPath.parent, f).path)
|
||||
isLooping = loop
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
currentTrack = track
|
||||
} catch (e: IOException) {
|
||||
Log.e("cddaStart", "Cannot play $f", e)
|
||||
player.reset()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (currentTrack > 0 && player.isPlaying) {
|
||||
player.stop()
|
||||
currentTrack = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun currentPosition(): Int {
|
||||
if (currentTrack == 0)
|
||||
return 0
|
||||
val frames = player.currentPosition * 75 / 1000
|
||||
return currentTrack or (frames shl 8)
|
||||
}
|
||||
|
||||
fun onActivityStop() {
|
||||
if (currentTrack > 0 && player.isPlaying) {
|
||||
player.pause()
|
||||
playerPaused = true
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityResume() {
|
||||
if (playerPaused) {
|
||||
player.start()
|
||||
playerPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MidiPlayer {
|
||||
private enum class State {
|
||||
STOPPED,
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
RELEASED,
|
||||
}
|
||||
|
||||
private val player = MediaPlayer()
|
||||
private var playing = false
|
||||
private var playerPaused = false
|
||||
private var state = State.STOPPED
|
||||
|
||||
fun start(path: String, loop: Boolean) {
|
||||
if (state == State.RELEASED) {
|
||||
return
|
||||
}
|
||||
state = State.STOPPED
|
||||
try {
|
||||
player.apply {
|
||||
reset()
|
||||
@@ -233,7 +191,7 @@ private class MidiPlayer {
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
playing = true
|
||||
state = State.PLAYING
|
||||
} catch (e: IOException) {
|
||||
Log.e("midiStart", "Cannot play midi", e)
|
||||
player.reset()
|
||||
@@ -241,27 +199,41 @@ private class MidiPlayer {
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (playing && player.isPlaying) {
|
||||
player.stop()
|
||||
playing = false
|
||||
when (state) {
|
||||
State.PLAYING, State.PAUSED -> {
|
||||
player.stop()
|
||||
state = State.STOPPED
|
||||
}
|
||||
State.STOPPED, State.RELEASED -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun currentPosition(): Int {
|
||||
return if (playing) player.currentPosition else 0
|
||||
return when (state) {
|
||||
State.PLAYING, State.PAUSED -> player.currentPosition
|
||||
State.STOPPED, State.RELEASED -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityStop() {
|
||||
if (playing && player.isPlaying) {
|
||||
if (state == State.PLAYING && player.isPlaying) {
|
||||
player.pause()
|
||||
playerPaused = true
|
||||
state = State.PAUSED
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityResume() {
|
||||
if (playerPaused) {
|
||||
if (state == State.PAUSED) {
|
||||
player.start()
|
||||
playerPaused = false
|
||||
state = State.PLAYING
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
if (state == State.RELEASED) {
|
||||
return
|
||||
}
|
||||
state = State.RELEASED
|
||||
player.release()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.UTFDataFormatException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
data class SelectedInstallFile(
|
||||
val uri: Uri,
|
||||
val displayName: String,
|
||||
)
|
||||
|
||||
class GameInstaller(private val store: GameStore) {
|
||||
suspend fun installZip(input: InputStream, progressCallback: suspend (String) -> Unit): File {
|
||||
val dir = store.createDirForGame()
|
||||
var committed = false
|
||||
try {
|
||||
val gameDir = extractFiles(input, dir, progressCallback)
|
||||
committed = true
|
||||
return gameDir
|
||||
} finally {
|
||||
if (!committed && !dir.deleteRecursively()) {
|
||||
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun installCdImage(
|
||||
files: List<SelectedInstallFile>,
|
||||
contentResolver: ContentResolver,
|
||||
tempDir: File,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
): File {
|
||||
val dir = store.createDirForGame()
|
||||
var committed = false
|
||||
try {
|
||||
val gameDir = extractCdImage(files, contentResolver, tempDir, dir, progressCallback)
|
||||
committed = true
|
||||
return gameDir
|
||||
} finally {
|
||||
if (!committed && !dir.deleteRecursively()) {
|
||||
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun installCdImageForTest(
|
||||
cdImage: CdImageReader,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
): File {
|
||||
val dir = store.createDirForGame()
|
||||
var committed = false
|
||||
try {
|
||||
val gameDir = extractCdImage(cdImage, dir, progressCallback)
|
||||
committed = true
|
||||
return gameDir
|
||||
} finally {
|
||||
if (!committed && !dir.deleteRecursively()) {
|
||||
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun extractFiles(
|
||||
input: InputStream,
|
||||
outDir: File,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
): File {
|
||||
val configWriter = GameConfigWriter()
|
||||
val hadDecodeError = forEachZipEntrySuspending(input) { zipEntry, zip ->
|
||||
Log.i("extractFiles", zipEntry.name)
|
||||
if (zipEntry.isDirectory)
|
||||
return@forEachZipEntrySuspending
|
||||
val resolvedPath = resolveOutputPath(outDir, zipEntry.name)
|
||||
val path = resolvedPath.file
|
||||
path.parentFile?.mkdirs()
|
||||
progressCallback(resolvedPath.relativePath)
|
||||
FileOutputStream(path).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
configWriter.maybeAdd(resolvedPath.relativePath)
|
||||
}
|
||||
if (!configWriter.ready) {
|
||||
if (hadDecodeError)
|
||||
throw InstallFailureException(R.string.unsupported_zip)
|
||||
throw InstallFailureException(R.string.cannot_find_ald)
|
||||
}
|
||||
configWriter.write(outDir)
|
||||
return configWriter.gameDir?.let { File(outDir, it) } ?: outDir
|
||||
}
|
||||
|
||||
private suspend fun extractCdImage(
|
||||
files: List<SelectedInstallFile>,
|
||||
contentResolver: ContentResolver,
|
||||
tempDir: File,
|
||||
outDir: File,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
): File {
|
||||
progressCallback("metadata parsing")
|
||||
CdImageReader.open(contentResolver, files, tempDir).use { cdImage ->
|
||||
return extractCdImage(cdImage, outDir, progressCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun extractCdImage(
|
||||
cdImage: CdImageReader,
|
||||
outDir: File,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
): File {
|
||||
val fs = Iso9660FileSystem(cdImage)
|
||||
val gameData = fs.findGameDataDirectory()
|
||||
?: throw InstallFailureException(R.string.cannot_find_game_data_directory)
|
||||
var foundAld = false
|
||||
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
|
||||
val resolvedPath = resolveOutputPath(outDir, path)
|
||||
resolvedPath.file.parentFile?.mkdirs()
|
||||
progressCallback(resolvedPath.relativePath)
|
||||
FileOutputStream(resolvedPath.file).buffered().use {
|
||||
input.copyTo(it)
|
||||
}
|
||||
if (File(path).name.matches(""".*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE))) {
|
||||
foundAld = true
|
||||
}
|
||||
}
|
||||
if (!foundAld) {
|
||||
throw InstallFailureException(R.string.cannot_find_ald)
|
||||
}
|
||||
File(outDir, Launcher.GAMEDIR_FILE).writeText("GAMEDATA")
|
||||
extractAudioTracks(cdImage, File(outDir, "GAMEDATA"), progressCallback)
|
||||
return File(outDir, "GAMEDATA")
|
||||
}
|
||||
|
||||
private suspend fun extractAudioTracks(
|
||||
cdImage: CdImageReader,
|
||||
gameDir: File,
|
||||
progressCallback: suspend (String) -> Unit
|
||||
) {
|
||||
val audioTracks = cdImage.audioTracks()
|
||||
if (audioTracks.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val cddaDir = File(gameDir, "cdda")
|
||||
cddaDir.mkdirs()
|
||||
val playlist = arrayOfNulls<String>(audioTracks.maxOrNull() ?: 0)
|
||||
for (track in audioTracks) {
|
||||
val relativePath = "cdda/track%02d.wav".format(track)
|
||||
val outputFile = File(gameDir, relativePath)
|
||||
progressCallback(relativePath)
|
||||
FileOutputStream(outputFile).buffered().use {
|
||||
cdImage.extractAudioTrack(track, it)
|
||||
}
|
||||
playlist[track - 1] = relativePath
|
||||
}
|
||||
File(gameDir, Launcher.PLAYLIST_FILE).writeText(playlist.joinToString("\n") { it ?: "" }.trimEnd('\n'))
|
||||
}
|
||||
}
|
||||
|
||||
internal class InstallFailureException(val msgId: Int) : Exception()
|
||||
|
||||
// A helper class which generates GAMEDIR_FILE and PLAYLIST_FILE.
|
||||
private class GameConfigWriter {
|
||||
var ready = false
|
||||
private set
|
||||
var gameDir: String? = null
|
||||
private set
|
||||
private val aldRegex = """.*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE)
|
||||
private val audioRegex = """((\d+).*|.*?(\d+))\.(wav|mp3|ogg)""".toRegex(RegexOption.IGNORE_CASE)
|
||||
private val audioFiles: Array<String?> = arrayOfNulls(100)
|
||||
|
||||
fun maybeAdd(path: String) {
|
||||
val name = File(path).name
|
||||
aldRegex.matchEntire(name)?.let {
|
||||
gameDir = File(path).parent
|
||||
ready = true
|
||||
}
|
||||
audioRegex.matchEntire(name)?.let {
|
||||
val track = it.groupValues[2].toIntOrNull() ?: it.groupValues[3].toInt()
|
||||
if (0 < track && track <= audioFiles.size)
|
||||
audioFiles[track - 1] = path
|
||||
}
|
||||
}
|
||||
|
||||
fun write(outDir: File) {
|
||||
// Generate GAMEDIR_FILE
|
||||
gameDir?.let {
|
||||
File(outDir, Launcher.GAMEDIR_FILE).writeText(it)
|
||||
}
|
||||
// Generate PLAYLIST_FILE
|
||||
val absGameDir = gameDir?.let { File(outDir, it) } ?: outDir
|
||||
val playlistFile = File(absGameDir, Launcher.PLAYLIST_FILE)
|
||||
if (!playlistFile.exists()) {
|
||||
val prefixToRemove = gameDir?.let { "$it/" } ?: ""
|
||||
val playlist = audioFiles.joinToString("\n") {
|
||||
it?.removePrefix(prefixToRemove) ?: ""
|
||||
}.trimEnd('\n')
|
||||
playlistFile.writeText(playlist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property file Safe canonical output path.
|
||||
* @property relativePath Path relative to the install root after canonicalization.
|
||||
*/
|
||||
internal data class ResolvedOutputPath(val file: File, val relativePath: String)
|
||||
|
||||
internal fun resolveOutputPath(baseDir: File, relativePath: String): ResolvedOutputPath {
|
||||
if (File(relativePath).isAbsolute) {
|
||||
throw IOException("Output path is absolute: $relativePath")
|
||||
}
|
||||
val canonicalBase = baseDir.canonicalFile
|
||||
val file = File(canonicalBase, relativePath).canonicalFile
|
||||
if (!isFileInsideDirectory(file, canonicalBase)) {
|
||||
throw IOException("Output path is outside target directory: $relativePath")
|
||||
}
|
||||
val basePath = canonicalBase.path + File.separator
|
||||
val canonicalRelativePath = file.path.removePrefix(basePath)
|
||||
return ResolvedOutputPath(file, canonicalRelativePath)
|
||||
}
|
||||
|
||||
private fun isFileInsideDirectory(file: File, directory: File): Boolean {
|
||||
return file.path.startsWith(directory.path + File.separator)
|
||||
}
|
||||
|
||||
internal fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit): Boolean =
|
||||
runBlocking {
|
||||
forEachZipEntrySuspending(input) { zipEntry, zip ->
|
||||
action(zipEntry, zip)
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun forEachZipEntrySuspending(
|
||||
input: InputStream,
|
||||
action: suspend (ZipEntry, ZipInputStream) -> Unit
|
||||
): Boolean {
|
||||
val zip = if (Build.VERSION.SDK_INT >= 24) {
|
||||
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
|
||||
} else {
|
||||
ZipInputStream(input.buffered())
|
||||
}
|
||||
var hadDecodeError = false
|
||||
zip.use {
|
||||
while (true) {
|
||||
try {
|
||||
val zipEntry = zip.nextEntry ?: break
|
||||
action(zipEntry, zip)
|
||||
} catch (e: UTFDataFormatException) {
|
||||
// Attempted to read Shift_JIS zip in Android < 7
|
||||
Log.w("forEachZipEntry", "UTFDataFormatException: skipping a zip entry")
|
||||
zip.closeEntry()
|
||||
hadDecodeError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hadDecodeError
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.UTFDataFormatException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class GameStore(private val rootDir: File) {
|
||||
data class Entry(val path: File, val title: String, val timestamp: Long)
|
||||
|
||||
private val gameList = arrayListOf<Entry>()
|
||||
val games: List<Entry>
|
||||
get() = gameList
|
||||
val titles: List<String>
|
||||
get() = gameList.map(Entry::title)
|
||||
|
||||
init {
|
||||
updateGameList()
|
||||
}
|
||||
|
||||
fun uninstall(id: Int) {
|
||||
gameList[id].path.deleteRecursively()
|
||||
gameList.removeAt(id)
|
||||
}
|
||||
|
||||
fun updateGameList() {
|
||||
var saveDirFound = false
|
||||
gameList.clear()
|
||||
for (path in rootDir.listFiles() ?: emptyArray()) {
|
||||
if (!path.isDirectory)
|
||||
continue
|
||||
if (path.name == Launcher.SAVE_DIR) {
|
||||
saveDirFound = true
|
||||
continue
|
||||
}
|
||||
try {
|
||||
val gameDirFile = File(path, Launcher.GAMEDIR_FILE)
|
||||
val gamePath = if (gameDirFile.exists()) File(path, gameDirFile.readText()) else path
|
||||
val titleFile = File(gamePath, Launcher.TITLE_FILE)
|
||||
val title = titleFile.readText()
|
||||
gameList.add(Entry(gamePath, title, titleFile.lastModified()))
|
||||
migratePlaylist(path)
|
||||
} catch (e: IOException) {
|
||||
// Incomplete game installation. Delete it.
|
||||
path.deleteRecursively()
|
||||
}
|
||||
}
|
||||
gameList.sortByDescending(Entry::timestamp)
|
||||
if (!saveDirFound) {
|
||||
File(rootDir, Launcher.SAVE_DIR).mkdir()
|
||||
}
|
||||
}
|
||||
|
||||
fun createDirForGame(): File {
|
||||
var i = 0
|
||||
while (true) {
|
||||
val f = File(rootDir, i++.toString())
|
||||
if (!f.exists() && f.mkdir()) {
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throws IOException
|
||||
fun exportSaveData(output: OutputStream) {
|
||||
ZipOutputStream(output.buffered()).use { zip ->
|
||||
for (path in File(rootDir, Launcher.SAVE_DIR).listFiles() ?: emptyArray()) {
|
||||
if (path.isDirectory || path.name.endsWith(".asd."))
|
||||
continue
|
||||
val pathInZip = "${Launcher.SAVE_DIR}/${path.name}"
|
||||
Log.i("exportSaveData", pathInZip)
|
||||
zip.putNextEntry(ZipEntry(pathInZip))
|
||||
path.inputStream().buffered().use {
|
||||
it.copyTo(zip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importSaveData(input: InputStream): Int? {
|
||||
try {
|
||||
var imported = false
|
||||
forEachZipEntry(input) { zipEntry, zip ->
|
||||
// Process only files directly under save/
|
||||
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/") ||
|
||||
zipEntry.name.count{it == '/'} != 1)
|
||||
return@forEachZipEntry
|
||||
val path = resolveOutputPath(rootDir, zipEntry.name).file
|
||||
Log.i("importSaveData", zipEntry.name)
|
||||
FileOutputStream(path).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
imported = true
|
||||
}
|
||||
return if (imported) null else R.string.no_data_to_import
|
||||
} catch (e: UTFDataFormatException) {
|
||||
// Attempted to read Shift_JIS zip in Android < 7
|
||||
return R.string.unsupported_zip
|
||||
} catch (e: IOException) {
|
||||
Log.e("launcher", "Failed to extract ZIP", e)
|
||||
return R.string.zip_extraction_error
|
||||
}
|
||||
}
|
||||
|
||||
fun clearSaveData(): Boolean {
|
||||
var success = true
|
||||
File(rootDir, Launcher.SAVE_DIR).listFiles()?.forEach { file ->
|
||||
if (!file.deleteRecursively()) success = false
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// Xsystem35-sdl2 2.3.0 - 2.11.1 used playlist2.txt. Rename it to playlist.txt.
|
||||
private fun migratePlaylist(dir: File) {
|
||||
val oldPlaylist = File(dir, Launcher.OLD_PLAYLIST_FILE)
|
||||
if (oldPlaylist.exists()) {
|
||||
oldPlaylist.renameTo(File(dir, Launcher.PLAYLIST_FILE))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.util.Locale
|
||||
|
||||
internal class Iso9660FileSystem(private val image: CdImageReader) {
|
||||
private val descriptor = readBestVolumeDescriptor()
|
||||
|
||||
data class Entry(
|
||||
val name: String,
|
||||
val extent: Int,
|
||||
val size: Int,
|
||||
val isDirectory: Boolean,
|
||||
)
|
||||
|
||||
fun findGameDataDirectory(): Entry? {
|
||||
return listDirectory(descriptor.rootDirectory).firstOrNull {
|
||||
it.isDirectory && it.name.uppercase(Locale.US) == "GAMEDATA"
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun extractDirectory(
|
||||
directory: Entry,
|
||||
outputPath: String,
|
||||
writeFile: suspend (String, InputStream) -> Unit,
|
||||
) {
|
||||
for (entry in listDirectory(directory)) {
|
||||
val childPath = "$outputPath/${entry.name}"
|
||||
if (entry.isDirectory) {
|
||||
extractDirectory(entry, childPath, writeFile)
|
||||
} else {
|
||||
writeFile(childPath, fileInputStream(entry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun listDirectory(directory: Entry): List<Entry> {
|
||||
val bytes = ByteArray(directory.size)
|
||||
image.readDataFully(
|
||||
directory.extent.toLong() * CdImageReader.ISO_SECTOR_SIZE,
|
||||
bytes,
|
||||
0,
|
||||
bytes.size
|
||||
)
|
||||
val entries = mutableListOf<Entry>()
|
||||
var offset = 0
|
||||
while (offset < bytes.size) {
|
||||
val length = bytes[offset].toInt() and 0xff
|
||||
if (length == 0) {
|
||||
offset = ((offset / CdImageReader.ISO_SECTOR_SIZE) + 1) * CdImageReader.ISO_SECTOR_SIZE
|
||||
continue
|
||||
}
|
||||
parseDirectoryRecord(bytes, offset, length)?.let { entries.add(it) }
|
||||
offset += length
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
private fun parseDirectoryRecord(bytes: ByteArray, offset: Int, length: Int): Entry? {
|
||||
if (length < 34) {
|
||||
throw InstallFailureException(R.string.invalid_iso9660_image)
|
||||
}
|
||||
val nameLength = bytes[offset + 32].toInt() and 0xff
|
||||
if (33 + nameLength > length) {
|
||||
throw InstallFailureException(R.string.invalid_iso9660_image)
|
||||
}
|
||||
val rawName = bytes.copyOfRange(offset + 33, offset + 33 + nameLength)
|
||||
if (rawName.size == 1 && (rawName[0].toInt() == 0 || rawName[0].toInt() == 1)) {
|
||||
return null
|
||||
}
|
||||
val name = descriptor.decodeName(rawName)
|
||||
val extent = readLittleEndianInt(bytes, offset + 2)
|
||||
val size = readLittleEndianInt(bytes, offset + 10)
|
||||
val flags = bytes[offset + 25].toInt() and 0xff
|
||||
return Entry(name, extent, size, flags and 0x02 != 0)
|
||||
}
|
||||
|
||||
private fun fileInputStream(entry: Entry): InputStream {
|
||||
return object : InputStream() {
|
||||
private var pos = 0
|
||||
|
||||
override fun read(): Int {
|
||||
val buffer = ByteArray(1)
|
||||
val read = read(buffer, 0, 1)
|
||||
return if (read < 0) -1 else buffer[0].toInt() and 0xff
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
if (pos >= entry.size) {
|
||||
return -1
|
||||
}
|
||||
val count = minOf(length, entry.size - pos)
|
||||
image.readDataFully(
|
||||
entry.extent.toLong() * CdImageReader.ISO_SECTOR_SIZE + pos,
|
||||
buffer,
|
||||
offset,
|
||||
count
|
||||
)
|
||||
pos += count
|
||||
return count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readBestVolumeDescriptor(): VolumeDescriptor {
|
||||
var primary: VolumeDescriptor? = null
|
||||
var joliet: VolumeDescriptor? = null
|
||||
val sector = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
var sectorNumber = 0x10
|
||||
while (true) {
|
||||
image.readSector(sectorNumber++, sector)
|
||||
if (String(sector, 1, 5, Charsets.US_ASCII) != "CD001") {
|
||||
throw InstallFailureException(R.string.invalid_iso9660_image)
|
||||
}
|
||||
when (sector[0].toInt() and 0xff) {
|
||||
1 -> primary = VolumeDescriptor(parseRootDirectory(sector), false)
|
||||
2 -> if (isJolietDescriptor(sector)) {
|
||||
joliet = VolumeDescriptor(parseRootDirectory(sector), true)
|
||||
}
|
||||
255 -> return joliet ?: primary
|
||||
?: throw InstallFailureException(R.string.invalid_iso9660_image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseRootDirectory(sector: ByteArray): Entry {
|
||||
return Entry(
|
||||
name = "",
|
||||
extent = readLittleEndianInt(sector, 156 + 2),
|
||||
size = readLittleEndianInt(sector, 156 + 10),
|
||||
isDirectory = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isJolietDescriptor(sector: ByteArray): Boolean {
|
||||
return sector[88] == 0x25.toByte() &&
|
||||
sector[89] == 0x2f.toByte() &&
|
||||
(sector[90] == 0x40.toByte() || sector[90] == 0x43.toByte() || sector[90] == 0x45.toByte())
|
||||
}
|
||||
|
||||
private data class VolumeDescriptor(
|
||||
val rootDirectory: Entry,
|
||||
val joliet: Boolean,
|
||||
) {
|
||||
fun decodeName(rawName: ByteArray): String {
|
||||
val charset = if (joliet) Charsets.UTF_16BE else Charset.forName("Shift_JIS")
|
||||
return String(rawName, charset).substringBefore(";")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readLittleEndianInt(bytes: ByteArray, offset: Int): Int {
|
||||
return (bytes[offset].toInt() and 0xff) or
|
||||
((bytes[offset + 1].toInt() and 0xff) shl 8) or
|
||||
((bytes[offset + 2].toInt() and 0xff) shl 16) or
|
||||
((bytes[offset + 3].toInt() and 0xff) shl 24)
|
||||
}
|
||||
@@ -17,18 +17,15 @@
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import android.content.ContentResolver
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.*
|
||||
import java.lang.StringBuilder
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
private var gLauncher: Launcher? = null
|
||||
|
||||
@@ -39,15 +36,22 @@ interface LauncherObserver {
|
||||
fun onInstallFailure(msgId: Int)
|
||||
}
|
||||
|
||||
private const val SAVE_DIR = "save"
|
||||
private const val PROGRESS = 0
|
||||
private const val SUCCESS = 1
|
||||
private const val FAILURE = 2
|
||||
sealed class InstallState {
|
||||
object Idle : InstallState()
|
||||
data class Installing(val progress: String?) : InstallState()
|
||||
data class Succeeded(val path: File, val archiveName: String?) : InstallState()
|
||||
data class Failed(val msgId: Int) : InstallState()
|
||||
}
|
||||
|
||||
class Launcher private constructor(private val rootDir: File) {
|
||||
data class InstallResult(val path: File, val archiveName: String?)
|
||||
|
||||
class Launcher private constructor(rootDir: File) {
|
||||
companion object {
|
||||
const val SAVE_DIR = "save"
|
||||
const val TITLE_FILE = "title.txt"
|
||||
const val GAMEDIR_FILE = "game_directory.txt"
|
||||
const val PLAYLIST_FILE = "playlist.txt"
|
||||
const val OLD_PLAYLIST_FILE = "playlist2.txt"
|
||||
|
||||
fun getInstance(rootDir: File): Launcher {
|
||||
if (gLauncher == null) {
|
||||
@@ -56,242 +60,118 @@ class Launcher private constructor(private val rootDir: File) {
|
||||
return gLauncher!!
|
||||
}
|
||||
|
||||
fun updateGameList() {
|
||||
gLauncher?.updateGameList()
|
||||
}
|
||||
}
|
||||
|
||||
data class Entry(val path: File, val title: String, val timestamp: Long)
|
||||
val games = arrayListOf<Entry>()
|
||||
private val store = GameStore(rootDir)
|
||||
private val installer = GameInstaller(store)
|
||||
val games: List<GameStore.Entry>
|
||||
get() = store.games
|
||||
val titles: List<String>
|
||||
get() = games.map(Entry::title)
|
||||
get() = store.titles
|
||||
var observer: LauncherObserver? = null
|
||||
var isInstalling = false
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private var installJob: Job? = null
|
||||
var installState: InstallState = InstallState.Idle
|
||||
private set
|
||||
|
||||
init {
|
||||
updateGameList()
|
||||
}
|
||||
|
||||
fun install(input: InputStream, archiveName: String?) {
|
||||
val dir = createDirForGame()
|
||||
@SuppressLint("HandlerLeak")
|
||||
val handler = object : Handler() {
|
||||
override fun handleMessage(msg: Message) {
|
||||
when (msg.what) {
|
||||
PROGRESS -> {
|
||||
observer?.onInstallProgress(msg.obj as String)
|
||||
}
|
||||
SUCCESS -> {
|
||||
isInstalling = false
|
||||
observer?.onInstallSuccess(msg.obj as File, archiveName)
|
||||
}
|
||||
FAILURE -> {
|
||||
isInstalling = false
|
||||
observer?.onInstallFailure(msg.obj as Int)
|
||||
fun installZip(input: InputStream, archiveName: String?) {
|
||||
if (installJob?.isActive == true) {
|
||||
input.close()
|
||||
return
|
||||
}
|
||||
startInstallJob {
|
||||
val gameDir = withContext(Dispatchers.IO) {
|
||||
input.use {
|
||||
installer.installZip(it) { msg ->
|
||||
withContext(Dispatchers.Main) {
|
||||
setInstallProgress(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallResult(gameDir, archiveName)
|
||||
}
|
||||
val t = Thread {
|
||||
extractFiles(input, dir, handler)
|
||||
}
|
||||
|
||||
fun installCdImage(
|
||||
files: List<SelectedInstallFile>,
|
||||
contentResolver: ContentResolver,
|
||||
tempDir: File,
|
||||
) {
|
||||
if (installJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
t.start()
|
||||
isInstalling = true
|
||||
startInstallJob {
|
||||
val gameDir = withContext(Dispatchers.IO) {
|
||||
installer.installCdImage(files, contentResolver, tempDir) { msg ->
|
||||
withContext(Dispatchers.Main) {
|
||||
setInstallProgress(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallResult(gameDir, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startInstallJob(block: suspend () -> InstallResult) {
|
||||
if (installJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
installState = InstallState.Installing(null)
|
||||
installJob = scope.launch {
|
||||
try {
|
||||
val result = block()
|
||||
setInstallSucceeded(result)
|
||||
} catch (e: InstallFailureException) {
|
||||
setInstallFailed(e.msgId)
|
||||
} catch (e: Exception) {
|
||||
Log.e("launcher", "Failed to install game", e)
|
||||
setInstallFailed(R.string.install_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeInstallResult() {
|
||||
if (installState is InstallState.Succeeded || installState is InstallState.Failed) {
|
||||
installState = InstallState.Idle
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInstallProgress(progress: String) {
|
||||
installState = InstallState.Installing(progress)
|
||||
observer?.onInstallProgress(progress)
|
||||
}
|
||||
|
||||
private fun setInstallSucceeded(result: InstallResult) {
|
||||
installState = InstallState.Succeeded(result.path, result.archiveName)
|
||||
observer?.onInstallSuccess(result.path, result.archiveName)
|
||||
}
|
||||
|
||||
private fun setInstallFailed(msgId: Int) {
|
||||
installState = InstallState.Failed(msgId)
|
||||
observer?.onInstallFailure(msgId)
|
||||
}
|
||||
|
||||
fun uninstall(id: Int) {
|
||||
games[id].path.deleteRecursively()
|
||||
games.removeAt(id)
|
||||
store.uninstall(id)
|
||||
observer?.onGameListChange()
|
||||
}
|
||||
|
||||
private fun updateGameList() {
|
||||
var saveDirFound = false
|
||||
games.clear()
|
||||
for (path in rootDir.listFiles()) {
|
||||
if (!path.isDirectory)
|
||||
continue
|
||||
if (path.name == SAVE_DIR) {
|
||||
saveDirFound = true
|
||||
continue
|
||||
}
|
||||
try {
|
||||
val titleFile = File(path, TITLE_FILE)
|
||||
val title = titleFile.readText()
|
||||
games.add(Entry(path, title, titleFile.lastModified()))
|
||||
} catch (e: IOException) {
|
||||
// Incomplete game installation. Delete it.
|
||||
path.deleteRecursively()
|
||||
}
|
||||
}
|
||||
games.sortByDescending(Entry::timestamp)
|
||||
if (!saveDirFound) {
|
||||
File(rootDir, SAVE_DIR).mkdir()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDirForGame(): File {
|
||||
var i = 0
|
||||
while (true) {
|
||||
val f = File(rootDir, i++.toString())
|
||||
if (!f.exists() && f.mkdir()) {
|
||||
return f
|
||||
}
|
||||
}
|
||||
fun refreshGameList() {
|
||||
store.updateGameList()
|
||||
observer?.onGameListChange()
|
||||
}
|
||||
|
||||
// Throws IOException
|
||||
fun exportSaveData(output: OutputStream) {
|
||||
ZipOutputStream(output.buffered()).use { zip ->
|
||||
for (path in File(rootDir, SAVE_DIR).listFiles()) {
|
||||
if (path.isDirectory || path.name.endsWith(".asd."))
|
||||
continue
|
||||
val pathInZip = "${SAVE_DIR}/${path.name}"
|
||||
Log.i("exportSaveData", pathInZip)
|
||||
zip.putNextEntry(ZipEntry(pathInZip))
|
||||
path.inputStream().buffered().use {
|
||||
it.copyTo(zip)
|
||||
}
|
||||
}
|
||||
}
|
||||
store.exportSaveData(output)
|
||||
}
|
||||
|
||||
fun importSaveData(input: InputStream): Int? {
|
||||
try {
|
||||
var imported = false
|
||||
forEachZipEntry(input) { zipEntry, zip ->
|
||||
// Process only files directly under save/
|
||||
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/") ||
|
||||
zipEntry.name.count{it == '/'} != 1)
|
||||
return@forEachZipEntry
|
||||
Log.i("importSaveData", zipEntry.name)
|
||||
FileOutputStream(File(rootDir, zipEntry.name)).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
imported = true
|
||||
}
|
||||
return if (imported) null else R.string.no_data_to_import
|
||||
} catch (e: UTFDataFormatException) {
|
||||
// Attempted to read Shift_JIS zip in Android < 7
|
||||
return R.string.unsupported_zip
|
||||
} catch (e: IOException) {
|
||||
Log.e("launcher", "Failed to extract ZIP", e)
|
||||
return R.string.zip_extraction_error
|
||||
}
|
||||
return store.importSaveData(input)
|
||||
}
|
||||
|
||||
private fun extractFiles(input: InputStream, outDir: File, handler: Handler) {
|
||||
try {
|
||||
val configWriter = GameConfigWriter()
|
||||
val hadDecodeError = forEachZipEntry(input) { zipEntry, zip ->
|
||||
Log.i("extractFiles", zipEntry.name)
|
||||
val path = File(outDir, zipEntry.name)
|
||||
if (zipEntry.isDirectory)
|
||||
return@forEachZipEntry
|
||||
path.parentFile.mkdirs()
|
||||
handler.sendMessage(handler.obtainMessage(PROGRESS, zipEntry.name))
|
||||
FileOutputStream(path).buffered().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
configWriter.maybeAdd(zipEntry.name)
|
||||
}
|
||||
if (!configWriter.readyToWrite()) {
|
||||
val msgId = if (hadDecodeError) R.string.unsupported_zip else R.string.cannot_find_ald
|
||||
handler.sendMessage(handler.obtainMessage(FAILURE, msgId))
|
||||
return
|
||||
}
|
||||
configWriter.write(outDir)
|
||||
handler.sendMessage(handler.obtainMessage(SUCCESS, outDir))
|
||||
} catch (e: IOException) {
|
||||
Log.e("launcher", "Failed to extract ZIP", e)
|
||||
handler.sendMessage(handler.obtainMessage(FAILURE, R.string.zip_extraction_error))
|
||||
}
|
||||
}
|
||||
|
||||
// A helper class which generates xsystem35.gr and playlist.txt in the game root directory.
|
||||
private class GameConfigWriter {
|
||||
private val grb = StringBuilder()
|
||||
private var basename: String? = null
|
||||
private val aldRegex = """(.*?)([a-z])([a-z])\.(ald|wai|bgi)""".toRegex(RegexOption.IGNORE_CASE)
|
||||
private val aldType = mapOf(
|
||||
"b" to "BGM",
|
||||
"d" to "Data",
|
||||
"g" to "Graphics",
|
||||
"m" to "Midi",
|
||||
"r" to "Resource",
|
||||
"s" to "Scenario",
|
||||
"w" to "Wave")
|
||||
private val specialResources = mapOf(
|
||||
"system39.ain" to "Ain",
|
||||
"system39.ini" to "Init",
|
||||
"sactefam.kld" to "SACT01"
|
||||
)
|
||||
private val audioRegex = """.*?(\d+)\.(wav|mp3|ogg)""".toRegex(RegexOption.IGNORE_CASE)
|
||||
private val audioFiles: Array<String?> = arrayOfNulls(100)
|
||||
|
||||
fun maybeAdd(path: String) {
|
||||
val name = File(path).name
|
||||
|
||||
specialResources[name.toLowerCase(Locale.US)]?.let {
|
||||
grb.appendln("$it $path")
|
||||
return
|
||||
}
|
||||
aldRegex.matchEntire(name)?.let {
|
||||
val ext = it.groupValues[4].toUpperCase(Locale.US)
|
||||
val type = if (ext == "ALD") {
|
||||
aldType[it.groupValues[2].toLowerCase(Locale.US)]
|
||||
} else {
|
||||
ext
|
||||
}
|
||||
val id = it.groupValues[3].toUpperCase(Locale.US)
|
||||
if (type != null) {
|
||||
grb.appendln("$type$id $path")
|
||||
basename = it.groupValues[1]
|
||||
}
|
||||
}
|
||||
audioRegex.matchEntire(name)?.let {
|
||||
val track = it.groupValues[1].toInt()
|
||||
if (track < audioFiles.size)
|
||||
audioFiles[track] = path
|
||||
}
|
||||
}
|
||||
|
||||
fun readyToWrite() = basename != null
|
||||
|
||||
fun write(outDir: File) {
|
||||
for (id in 'A' .. 'Z') {
|
||||
grb.appendln("Save$id ../save/${basename}s${id.toLowerCase()}.asd")
|
||||
}
|
||||
val gr = grb.toString()
|
||||
Log.i("xsystem35.gr", gr)
|
||||
File(outDir, "xsystem35.gr").writeText(gr)
|
||||
|
||||
val playlist = audioFiles.joinToString("\n") { it ?: "" }.trimEnd('\n')
|
||||
File(outDir, PLAYLIST_FILE).writeText(playlist)
|
||||
}
|
||||
fun clearSaveData(): Boolean {
|
||||
return store.clearSaveData()
|
||||
}
|
||||
}
|
||||
|
||||
private fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit): Boolean {
|
||||
val zip = if (Build.VERSION.SDK_INT >= 24) {
|
||||
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
|
||||
} else {
|
||||
ZipInputStream(input.buffered())
|
||||
}
|
||||
var hadDecodeError = false
|
||||
zip.use {
|
||||
while (true) {
|
||||
try {
|
||||
val zipEntry = zip.nextEntry ?: break
|
||||
action(zipEntry, zip)
|
||||
} catch (e: UTFDataFormatException) {
|
||||
// Attempted to read Shift_JIS zip in Android < 7
|
||||
Log.w("forEachZipEntry", "UTFDataFormatException: skipping a zip entry")
|
||||
zip.closeEntry()
|
||||
hadDecodeError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hadDecodeError
|
||||
}
|
||||
|
||||
@@ -22,62 +22,67 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import java.io.*
|
||||
|
||||
private const val CONTENT_TYPE_ZIP = "application/zip"
|
||||
private const val INSTALL_REQUEST = 1
|
||||
private const val ZIP_INSTALL_REQUEST = 1
|
||||
private const val SAVEDATA_EXPORT_REQUEST = 2
|
||||
private const val SAVEDATA_IMPORT_REQUEST = 3
|
||||
private const val CD_IMAGE_INSTALL_REQUEST = 4
|
||||
|
||||
class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, LauncherObserver {
|
||||
class LauncherActivity : Activity(), LauncherObserver {
|
||||
private lateinit var launcher: Launcher
|
||||
private var progressDialog: ProgressDialogFragment? = null
|
||||
private var progressDialog: Dialog? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.launcher)
|
||||
|
||||
launcher = Launcher.getInstance(filesDir)
|
||||
launcher.observer = this
|
||||
if (launcher.isInstalling) {
|
||||
showProgressDialog()
|
||||
}
|
||||
renderInstallState(launcher.installState)
|
||||
|
||||
onGameListChange()
|
||||
listView.onItemLongClickListener = this
|
||||
val listView = findViewById<ListView>(R.id.list)
|
||||
listView.emptyView = findViewById(R.id.empty)
|
||||
findViewById<TextView>(R.id.usage).text = Html.fromHtml(getString(R.string.usage))
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
onListItemClick(position)
|
||||
}
|
||||
listView.setOnItemLongClickListener { _, _, position, _ ->
|
||||
onItemLongClick(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
launcher.observer = null
|
||||
dismissProgressDialog()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onListItemClick(l: ListView?, v: View?, position: Int, id: Long) {
|
||||
super.onListItemClick(l, v, position, id)
|
||||
if (position < launcher.games.size) {
|
||||
startGame(launcher.games[position].path, null)
|
||||
} else {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), INSTALL_REQUEST)
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
launcher.refreshGameList()
|
||||
}
|
||||
|
||||
override fun onItemLongClick(a: AdapterView<*>?, v: View?, position: Int, id: Long): Boolean {
|
||||
if (position < launcher.games.size) {
|
||||
AlertDialog.Builder(this).setTitle(R.string.uninstall_dialog_title)
|
||||
.setMessage(getString(R.string.uninstall_dialog_message, launcher.games[position].title))
|
||||
.setPositiveButton(R.string.ok) {_, _ -> uninstall(position)}
|
||||
.setNegativeButton(R.string.cancel) {_, _ -> }
|
||||
.show()
|
||||
}
|
||||
private fun onListItemClick(position: Int) {
|
||||
startGame(launcher.games[position].path, null)
|
||||
}
|
||||
|
||||
private fun onItemLongClick(position: Int): Boolean {
|
||||
AlertDialog.Builder(this).setTitle(R.string.confirm)
|
||||
.setMessage(getString(R.string.uninstall_dialog_message, launcher.games[position].title))
|
||||
.setPositiveButton(R.string.ok) {_, _ -> uninstall(position)}
|
||||
.setNegativeButton(R.string.cancel) {_, _ -> }
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -88,6 +93,20 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.install_from_zip -> {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), ZIP_INSTALL_REQUEST)
|
||||
true
|
||||
}
|
||||
R.id.install_from_cd_image -> {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.type = "*/*"
|
||||
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_cd_image_files)),
|
||||
CD_IMAGE_INSTALL_REQUEST)
|
||||
true
|
||||
}
|
||||
R.id.export_savedata -> {
|
||||
val i = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
i.type = CONTENT_TYPE_ZIP
|
||||
@@ -102,6 +121,32 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
SAVEDATA_IMPORT_REQUEST)
|
||||
true
|
||||
}
|
||||
R.id.clear_savedata -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.confirm)
|
||||
.setMessage(R.string.clear_save_data_confirm)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (launcher.clearSaveData()) {
|
||||
Toast.makeText(this, R.string.save_data_clear_success, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
errorDialog(R.string.save_data_clear_error)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.help -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(Html.fromHtml(getString(R.string.usage)))
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.licenses -> {
|
||||
val intent = Intent(this, LicensesMenuActivity::class.java)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -109,14 +154,20 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode != RESULT_OK)
|
||||
return
|
||||
val uri = data?.data ?: return
|
||||
when (requestCode) {
|
||||
INSTALL_REQUEST -> {
|
||||
ZIP_INSTALL_REQUEST -> {
|
||||
val uri = data?.data ?: return
|
||||
val input = contentResolver.openInputStream(uri) ?: return
|
||||
showProgressDialog()
|
||||
launcher.install(input, getArchiveName(uri))
|
||||
launcher.installZip(input, getArchiveName(uri))
|
||||
renderInstallState(launcher.installState)
|
||||
}
|
||||
CD_IMAGE_INSTALL_REQUEST -> {
|
||||
val files = getSelectedInstallFiles(data)
|
||||
launcher.installCdImage(files, contentResolver, cacheDir)
|
||||
renderInstallState(launcher.installState)
|
||||
}
|
||||
SAVEDATA_EXPORT_REQUEST -> try {
|
||||
val uri = data?.data ?: return
|
||||
launcher.exportSaveData(contentResolver.openOutputStream(uri)!!)
|
||||
Toast.makeText(this, R.string.save_data_export_success, Toast.LENGTH_SHORT).show()
|
||||
} catch (e: IOException) {
|
||||
@@ -124,6 +175,7 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
errorDialog(R.string.save_data_export_error)
|
||||
}
|
||||
SAVEDATA_IMPORT_REQUEST -> {
|
||||
val uri = data?.data ?: return
|
||||
val input = contentResolver.openInputStream(uri) ?: return
|
||||
val errMsgId = launcher.importSaveData(input)
|
||||
if (errMsgId == null) {
|
||||
@@ -138,28 +190,27 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
|
||||
override fun onGameListChange() {
|
||||
val items = launcher.titles.toMutableList()
|
||||
items.add(getString(R.string.install_from_zip))
|
||||
listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
|
||||
val listView = findViewById<ListView>(R.id.list)
|
||||
listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
|
||||
}
|
||||
|
||||
override fun onInstallProgress(path: String) {
|
||||
progressDialog?.setProgress(getString(R.string.install_progress, path))
|
||||
renderInstallState(launcher.installState)
|
||||
}
|
||||
|
||||
override fun onInstallSuccess(path: File, archiveName: String?) {
|
||||
dismissProgressDialog()
|
||||
startGame(path, archiveName)
|
||||
renderInstallState(launcher.installState)
|
||||
}
|
||||
|
||||
override fun onInstallFailure(msgId: Int) {
|
||||
dismissProgressDialog()
|
||||
errorDialog(msgId)
|
||||
renderInstallState(launcher.installState)
|
||||
}
|
||||
|
||||
private fun startGame(path: File, archiveName: String?) {
|
||||
val i = Intent()
|
||||
i.setClass(applicationContext, GameActivity::class.java)
|
||||
i.putExtra(GameActivity.EXTRA_GAME_ROOT, path.path)
|
||||
i.putExtra(GameActivity.EXTRA_SAVE_DIRECTORY, File(filesDir, Launcher.SAVE_DIR).path)
|
||||
i.putExtra(GameActivity.EXTRA_ARCHIVE_NAME, archiveName)
|
||||
startActivity(i)
|
||||
}
|
||||
@@ -168,9 +219,36 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
launcher.uninstall(id)
|
||||
}
|
||||
|
||||
private fun showProgressDialog() {
|
||||
progressDialog = ProgressDialogFragment()
|
||||
progressDialog!!.show(fragmentManager, "progress_dialog")
|
||||
private fun renderInstallState(state: InstallState) {
|
||||
when (state) {
|
||||
InstallState.Idle -> dismissProgressDialog()
|
||||
is InstallState.Installing -> showProgressDialog(state.progress)
|
||||
is InstallState.Succeeded -> {
|
||||
dismissProgressDialog()
|
||||
startGame(state.path, state.archiveName)
|
||||
launcher.consumeInstallResult()
|
||||
}
|
||||
is InstallState.Failed -> {
|
||||
dismissProgressDialog()
|
||||
errorDialog(state.msgId)
|
||||
launcher.consumeInstallResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showProgressDialog(progress: String? = null) {
|
||||
if (progressDialog == null) {
|
||||
progressDialog = Dialog(this)
|
||||
progressDialog!!.apply {
|
||||
setTitle(R.string.install_dialog_title)
|
||||
setCancelable(false)
|
||||
setContentView(R.layout.progress_dialog)
|
||||
show()
|
||||
}
|
||||
}
|
||||
progress?.let {
|
||||
progressDialog?.findViewById<TextView>(R.id.text)?.text = getString(R.string.install_progress, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissProgressDialog() {
|
||||
@@ -186,28 +264,36 @@ class LauncherActivity : ListActivity(), AdapterView.OnItemLongClickListener, La
|
||||
}
|
||||
|
||||
private fun getArchiveName(uri: Uri): String? {
|
||||
val fname = getDisplayName(uri) ?: return null
|
||||
return if (fname.endsWith(".zip", true)) fname.dropLast(4) else fname
|
||||
}
|
||||
|
||||
private fun getSelectedInstallFiles(data: Intent?): List<SelectedInstallFile> {
|
||||
val files = mutableListOf<SelectedInstallFile>()
|
||||
val clipData = data?.clipData
|
||||
if (clipData != null) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
files.add(SelectedInstallFile(uri, getDisplayName(uri) ?: uri.lastPathSegment ?: ""))
|
||||
}
|
||||
} else {
|
||||
data?.data?.let { uri ->
|
||||
files.add(SelectedInstallFile(uri, getDisplayName(uri) ?: uri.lastPathSegment ?: ""))
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
private fun getDisplayName(uri: Uri): String? {
|
||||
val cursor = contentResolver.query(uri, null, null, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val fname = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||
return if (fname.endsWith(".zip", true)) fname.dropLast(4) else fname
|
||||
val column = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (column >= 0) {
|
||||
return it.getString(column)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION") // for ProgressDialog
|
||||
class ProgressDialogFragment : DialogFragment() {
|
||||
private lateinit var dialog: ProgressDialog
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
dialog = ProgressDialog(activity)
|
||||
return dialog.apply {
|
||||
setTitle(R.string.install_dialog_title)
|
||||
setCancelable(true)
|
||||
}
|
||||
}
|
||||
fun setProgress(msg: String) {
|
||||
dialog.setMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import java.io.BufferedReader
|
||||
|
||||
class LicensesActivity : Activity() {
|
||||
companion object {
|
||||
const val EXTRA_DISPLAY_NAME = "DISPLAY_NAME"
|
||||
const val EXTRA_FILE_NAME = "FILE_NAME"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_licenses)
|
||||
|
||||
actionBar?.title = intent.getStringExtra(EXTRA_DISPLAY_NAME)
|
||||
val path = "licenses/" + intent.getStringExtra(EXTRA_FILE_NAME)
|
||||
val text = assets.open(path).bufferedReader().use(BufferedReader::readText)
|
||||
findViewById<TextView>(R.id.license_text).text = text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.ListView
|
||||
import android.widget.SimpleAdapter
|
||||
|
||||
class LicensesMenuActivity : Activity() {
|
||||
class Entry(val displayName: String, val fileName: String, val url: String)
|
||||
private val entries: ArrayList<Entry> = arrayListOf(
|
||||
Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"),
|
||||
Entry("SDL", "SDL", "https://www.libsdl.org/"),
|
||||
Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"),
|
||||
Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"),
|
||||
Entry("microui", "microui", "https://github.com/rxi/microui"),
|
||||
Entry("FreeType", "freetype", "https://freetype.org/"),
|
||||
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
|
||||
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
|
||||
)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_licenses_menu)
|
||||
|
||||
val items = entries.map { mapOf("name" to it.displayName, "url" to it.url)}
|
||||
val listView = findViewById<ListView>(R.id.list)
|
||||
listView.adapter = SimpleAdapter(this, items, android.R.layout.simple_list_item_2,
|
||||
arrayOf("name", "url"), intArrayOf(android.R.id.text1, android.R.id.text2))
|
||||
listView.setOnItemClickListener { _, _, pos, _ ->
|
||||
val intent = Intent(this, LicensesActivity::class.java).apply {
|
||||
putExtra(LicensesActivity.EXTRA_DISPLAY_NAME, entries[pos].displayName)
|
||||
putExtra(LicensesActivity.EXTRA_FILE_NAME, entries[pos].fileName)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
@@ -429,7 +429,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
@@ -564,10 +564,10 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
|
||||
@@ -105,36 +105,6 @@ public class HIDDeviceManager {
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
// Make sure we have the HIDAPI library loaded with the native functions
|
||||
try {
|
||||
SDL.loadLibrary("hidapi");
|
||||
} catch (Throwable e) {
|
||||
Log.w(TAG, "Couldn't load hidapi: " + e.toString());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setCancelable(false);
|
||||
builder.setTitle("SDL HIDAPI Error");
|
||||
builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage());
|
||||
builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
// If our context is an activity, exit rather than crashing when we can't
|
||||
// call our native functions.
|
||||
Activity activity = (Activity)context;
|
||||
|
||||
activity.finish();
|
||||
}
|
||||
catch (ClassCastException cce) {
|
||||
// Context wasn't an activity, there's nothing we can do. Give up and return.
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
@@ -149,9 +119,6 @@ public class HIDDeviceManager {
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
|
||||
initializeUSB();
|
||||
initializeBluetooth();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
@@ -174,6 +141,9 @@ public class HIDDeviceManager {
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
@@ -200,7 +170,7 @@ public class HIDDeviceManager {
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
@@ -278,7 +248,11 @@ public class HIDDeviceManager {
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
@@ -299,13 +273,20 @@ public class HIDDeviceManager {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0b05, // ASUS
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
@@ -379,12 +360,19 @@ public class HIDDeviceManager {
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
@@ -549,7 +537,7 @@ public class HIDDeviceManager {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -571,6 +559,18 @@ public class HIDDeviceManager {
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
@@ -584,7 +584,20 @@ public class HIDDeviceManager {
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0));
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
||||
} else {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
|
||||
@@ -52,7 +52,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
@@ -86,7 +86,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
|
||||
@@ -29,6 +29,7 @@ public class SDL {
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@@ -37,6 +38,10 @@ public class SDL {
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
loadLibrary(libraryName, mContext);
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
@@ -52,10 +57,10 @@ public class SDL {
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
@@ -65,7 +70,7 @@ public class SDL {
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,8 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
@@ -8,34 +11,67 @@ import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
public class SDLAudioManager
|
||||
{
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
@@ -43,10 +79,14 @@ public class SDLAudioManager
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
@@ -55,7 +95,7 @@ public class SDLAudioManager
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 : 21);
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
@@ -116,7 +156,7 @@ public class SDLAudioManager
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
@@ -197,6 +237,10 @@ public class SDLAudioManager
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
@@ -220,6 +264,10 @@ public class SDLAudioManager
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
@@ -234,11 +282,73 @@ public class SDLAudioManager
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,6 +360,11 @@ public class SDLAudioManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
@@ -322,18 +437,22 @@ public class SDLAudioManager
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
@@ -342,7 +461,7 @@ public class SDLAudioManager
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
@@ -387,4 +506,9 @@ public class SDLAudioManager
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SDLControllerManager
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int nhats, int nballs);
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
@@ -42,7 +42,7 @@ public class SDLControllerManager
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
@@ -50,7 +50,7 @@ public class SDLControllerManager
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
@@ -168,6 +168,32 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +236,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,23 +281,21 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,6 +317,9 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
@@ -310,6 +337,43 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
@@ -319,6 +383,7 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
@@ -360,6 +425,7 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
@@ -480,13 +546,15 @@ class SDLHapticHandler {
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
if (vib != null) {
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,8 +628,6 @@ class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
case InputDevice.SOURCE_GAMEPAD:
|
||||
case InputDevice.SOURCE_DPAD:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
@@ -691,8 +757,6 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
case InputDevice.SOURCE_GAMEPAD:
|
||||
case InputDevice.SOURCE_DPAD:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
@@ -747,7 +811,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -757,7 +821,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView android:id="@+id/license_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp" />
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<ListView android:id="@+id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ListView android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<ScrollView android:id="@+id/empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:id="@+id/usage"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp" />
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textSize="16sp"/>
|
||||
</LinearLayout>
|
||||
@@ -1,9 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/install_from_zip"
|
||||
android:title="@string/action_install_from_zip" />
|
||||
<item
|
||||
android:id="@+id/install_from_cd_image"
|
||||
android:title="@string/action_install_from_cd_image" />
|
||||
<item
|
||||
android:id="@+id/export_savedata"
|
||||
android:title="@string/action_export_save_data" />
|
||||
<item
|
||||
android:id="@+id/import_savedata"
|
||||
android:title="@string/action_import_save_data" />
|
||||
</menu>
|
||||
<item
|
||||
android:id="@+id/clear_savedata"
|
||||
android:title="@string/action_clear_save_data" />
|
||||
<item
|
||||
android:id="@+id/help"
|
||||
android:title="@string/action_help" />
|
||||
<item
|
||||
android:id="@+id/licenses"
|
||||
android:title="@string/action_licenses" />
|
||||
</menu>
|
||||
|
||||
@@ -2,20 +2,51 @@
|
||||
<resources>
|
||||
<string name="app_name">xsystem35</string>
|
||||
<string name="action_export_save_data">セーブデータをエクスポート</string>
|
||||
<string name="action_help">使い方</string>
|
||||
<string name="action_import_save_data">セーブデータをインポート</string>
|
||||
<string name="action_install_from_cd_image">CDイメージからインストール</string>
|
||||
<string name="action_install_from_zip">ZIPからインストール</string>
|
||||
<string name="action_licenses">オープンソースライセンス</string>
|
||||
<string name="action_clear_save_data">セーブデータをクリア</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="cannot_find_ald">System 3.x のファイル (*.ald) が見つかりません。</string>
|
||||
<string name="cannot_find_game_data_directory">CDイメージ内に GAMEDATA ディレクトリが見つかりません。</string>
|
||||
<string name="cd_image_read_error">選択された CD イメージファイルを読み取れません。</string>
|
||||
<string name="choose_a_file">ファイルを選択</string>
|
||||
<string name="choose_cd_image_files">CDイメージファイルを選択</string>
|
||||
<string name="clear_save_data_confirm">セーブデータをすべて削除します。元には戻せません。本当によろしいですか?</string>
|
||||
<string name="confirm">確認</string>
|
||||
<string name="error_dialog_title">エラー</string>
|
||||
<string name="install_dialog_title">インストール中…</string>
|
||||
<string name="install_from_zip">ZIP からインストール</string>
|
||||
<string name="install_error">ゲームのインストールに失敗しました。</string>
|
||||
<string name="install_progress">%s を展開中</string>
|
||||
<string name="invalid_iso9660_image">有効な ISO9660 イメージではありません。</string>
|
||||
<string name="missing_cd_image_metadata">CD イメージファイルと対応するメタデータファイルを一緒に選択してください。ファイル選択画面ではファイルを長押しすると複数選択モードに入れます。</string>
|
||||
<string name="no_data_to_import">ZIP アーカイブ中に save/ フォルダが見つかりません。</string>
|
||||
<string name="save_data_export_error">セーブデータのエクスポートに失敗しました。</string>
|
||||
<string name="save_data_export_success">エクスポートに成功しました。</string>
|
||||
<string name="save_data_import_success">インポートに成功しました。</string>
|
||||
<string name="save_data_clear_success">セーブデータをクリアしました。</string>
|
||||
<string name="save_data_clear_error">セーブデータのクリアに失敗しました。</string>
|
||||
<string name="uninstall_dialog_message">「%s」をアンインストールしますか?</string>
|
||||
<string name="uninstall_dialog_title">確認</string>
|
||||
<string name="unsupported_cd_image">この CD イメージ形式にはまだ対応していません。現在は .iso、.bin/.img + .cue/.ccd、.mdf/.mds に対応しています。</string>
|
||||
<string name="unsupported_zip">この形式の ZIP はサポートしていません。</string>
|
||||
<string name="zip_extraction_error">ZIP の展開に失敗しました。</string>
|
||||
</resources>
|
||||
<string name="usage">
|
||||
<![CDATA[
|
||||
<h1>ゲームのインストール方法</h1>
|
||||
<p>1. PC上で、ゲームのファイルが入ったZIPを用意します。例えば以下のようなファイルを含めます:</p>
|
||||
<div>
|
||||
・鬼畜王SA.ALD<br>
|
||||
・鬼畜王GA.ALD<br>
|
||||
・鬼畜王GB.ALD<br>
|
||||
・鬼畜王WA.ALD<br>
|
||||
・02.mp3<br>
|
||||
・03.mp3<br>
|
||||
・…<br>
|
||||
</div>
|
||||
<p>2. ZIPファイルを (例えばUSB経由で) PCからデバイスに転送します。</p>
|
||||
<p>3. この画面の右上のメニューボタンをタップして「ZIPからインストール」を選択し、転送したZIPを選びます。</p>
|
||||
]]>
|
||||
</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,21 +1,53 @@
|
||||
<resources>
|
||||
<string name="app_name">xsystem35</string>
|
||||
<string name="action_clear_save_data">Clear Save Files</string>
|
||||
<string name="action_export_save_data">Export Save Files</string>
|
||||
<string name="action_help">Help</string>
|
||||
<string name="action_import_save_data">Import Save Files</string>
|
||||
<string name="action_install_from_cd_image">Install from CD image</string>
|
||||
<string name="action_install_from_zip">Install from ZIP</string>
|
||||
<string name="action_licenses">Open source licenses</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="cannot_find_ald">Cannot find System 3.x game files (*.ald).</string>
|
||||
<string name="cannot_find_game_data_directory">Cannot find GAMEDATA directory in the CD image.</string>
|
||||
<string name="cd_image_read_error">Cannot read the selected CD image file.</string>
|
||||
<string name="choose_a_file">Choose a file</string>
|
||||
<string name="choose_cd_image_files">Choose CD image files</string>
|
||||
<string name="clear_save_data_confirm">Are you sure you want to clear all save files? This cannot be undone.</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="error_dialog_title">Error</string>
|
||||
<string name="install_dialog_title">Installing…</string>
|
||||
<string name="install_from_zip">Install from ZIP</string>
|
||||
<string name="install_error">Failed to install game.</string>
|
||||
<string name="install_progress">Extracting %s</string>
|
||||
<string name="invalid_iso9660_image">This file is not a valid ISO9660 image.</string>
|
||||
<string name="missing_cd_image_metadata">Select a matching metadata file with the CD image file. In the file picker, long-press a file to enter multi-select mode.</string>
|
||||
<string name="no_data_to_import">No save/ folder in the ZIP archive.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="save_data_clear_error">Failed to clear save files.</string>
|
||||
<string name="save_data_clear_success">Save files cleared.</string>
|
||||
<string name="save_data_export_error">Failed to export save files.</string>
|
||||
<string name="save_data_import_success">Imported successfully.</string>
|
||||
<string name="save_data_export_success">Exported successfully.</string>
|
||||
<string name="uninstall_dialog_message">Uninstall \"%s\"?</string>
|
||||
<string name="uninstall_dialog_title">Confirm</string>
|
||||
<string name="unsupported_cd_image">This CD image format is not supported yet. Currently only .iso, .bin/.img + .cue/.ccd, and .mdf/.mds are supported.</string>
|
||||
<string name="unsupported_zip">This type of ZIP is not supported.</string>
|
||||
<string name="zip_extraction_error">Failed to extract ZIP.</string>
|
||||
<string name="usage">
|
||||
<![CDATA[
|
||||
<h1>How to install a game</h1>
|
||||
<p>1. On your PC, prepare a ZIP containing the game files. For example, it should contain the following files:</p>
|
||||
<div>
|
||||
* KICHIKUSA.ALD<br>
|
||||
* KICHIKUGA.ALD<br>
|
||||
* KICHIKUGB.ALD<br>
|
||||
* KICHIKUWA.ALD<br>
|
||||
* System39.ain<br>
|
||||
* 02.mp3<br>
|
||||
* 03.mp3<br>
|
||||
* …<br>
|
||||
</div>
|
||||
<p>2. Transfer the ZIP file from your PC to the device (e.g. via USB).</p>
|
||||
<p>3. Tap the menu button at the upper-right of this screen, select "Install from ZIP," and choose the ZIP you transferred.</p>
|
||||
]]>
|
||||
</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,714 @@
|
||||
/* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
package io.github.kichikuou.xsystem35
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class Iso9660FileSystemTest {
|
||||
@get:Rule
|
||||
val tmp = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun extractsGameDataDirectoryFromPrimaryDescriptor() = runBlocking {
|
||||
val iso = SyntheticIsoBuilder()
|
||||
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
|
||||
.addGameDataFile("SUB/FILE.TXT;1", "text".toByteArray())
|
||||
.writeTo(tmp.newFile("game.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
val fs = Iso9660FileSystem(reader)
|
||||
val gameData = fs.findGameDataDirectory()
|
||||
?: throw AssertionError("GAMEDATA directory was not found")
|
||||
val extracted = mutableMapOf<String, ByteArray>()
|
||||
|
||||
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
|
||||
extracted[path] = input.readBytes()
|
||||
}
|
||||
|
||||
assertArrayEquals("ald".toByteArray(), extracted["GAMEDATA/SA.ALD"])
|
||||
assertArrayEquals("text".toByteArray(), extracted["GAMEDATA/SUB/FILE.TXT"])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodesPrimaryDescriptorNamesAsShiftJis() = runBlocking {
|
||||
val name = "表.TXT;1"
|
||||
val iso = SyntheticIsoBuilder()
|
||||
.addGameDataFile(name, "sjis".toByteArray())
|
||||
.writeTo(tmp.newFile("sjis.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
val fs = Iso9660FileSystem(reader)
|
||||
val gameData = fs.findGameDataDirectory()
|
||||
?: throw AssertionError("GAMEDATA directory was not found")
|
||||
val extracted = mutableMapOf<String, ByteArray>()
|
||||
|
||||
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
|
||||
extracted[path] = input.readBytes()
|
||||
}
|
||||
|
||||
assertArrayEquals("sjis".toByteArray(), extracted["GAMEDATA/表.TXT"])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prefersJolietDescriptorOverPrimaryDescriptor() = runBlocking {
|
||||
val iso = SyntheticIsoBuilder()
|
||||
.addGameDataFile("PRIMARY.TXT;1", "primary".toByteArray())
|
||||
.addJolietGameDataFile("日本語.TXT;1", "joliet".toByteArray())
|
||||
.writeTo(tmp.newFile("joliet.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
val fs = Iso9660FileSystem(reader)
|
||||
val gameData = fs.findGameDataDirectory()
|
||||
?: throw AssertionError("GAMEDATA directory was not found")
|
||||
val extracted = mutableMapOf<String, ByteArray>()
|
||||
|
||||
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
|
||||
extracted[path] = input.readBytes()
|
||||
}
|
||||
|
||||
assertFalse(extracted.containsKey("GAMEDATA/PRIMARY.TXT"))
|
||||
assertArrayEquals("joliet".toByteArray(), extracted["GAMEDATA/日本語.TXT"])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsInvalidVolumeDescriptorSignature() {
|
||||
val iso = tmp.newFile("invalid.iso")
|
||||
iso.writeBytes(ByteArray(18 * CdImageReader.ISO_SECTOR_SIZE))
|
||||
|
||||
val e = assertInstallFailure(R.string.invalid_iso9660_image) {
|
||||
CdImageReader.openForFile(iso).use { Iso9660FileSystem(it) }
|
||||
}
|
||||
assertEquals(R.string.invalid_iso9660_image, e.msgId)
|
||||
}
|
||||
}
|
||||
|
||||
class CdImageReaderTest {
|
||||
@get:Rule
|
||||
val tmp = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun readsIsoSectorAt2048ByteOffset() {
|
||||
val sector0 = sectorFilledWith(0x10)
|
||||
val sector1 = sectorFilledWith(0x20)
|
||||
val image = tmp.newFile("plain.iso")
|
||||
image.writeBytes(sector0 + sector1)
|
||||
|
||||
CdImageReader.openForFile(image).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
|
||||
reader.readSector(1, buffer)
|
||||
|
||||
assertArrayEquals(sector1, buffer)
|
||||
assertTrue(reader.audioTracks().isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cueMode12048ReadsDataSector() {
|
||||
val payload = sectorFilledWith(0x31)
|
||||
val image = tmp.newFile("mode2048.bin")
|
||||
image.writeBytes(payload)
|
||||
|
||||
CdImageReader.openForFile(image, "mode2048.cue", cue("MODE1/2048").toByteArray()).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
|
||||
reader.readSector(0, buffer)
|
||||
|
||||
assertArrayEquals(payload, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cueMode12352ReadsPayloadAtOffset16() {
|
||||
val payload = sectorFilledWith(0x42)
|
||||
val image = tmp.newFile("mode2352.bin")
|
||||
image.writeBytes(rawDataSector(payload, 0x11))
|
||||
|
||||
CdImageReader.openForFile(image, "mode2352.cue", cue("MODE1/2352").toByteArray()).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
|
||||
reader.readSector(0, buffer)
|
||||
|
||||
assertArrayEquals(payload, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cuePregapAdvancesDataTrackStart() {
|
||||
val pregapPayload = sectorFilledWith(0x21)
|
||||
val dataPayload = sectorFilledWith(0x22)
|
||||
val image = tmp.newFile("pregap.bin")
|
||||
image.writeBytes(rawDataSector(pregapPayload, 0x01) + rawDataSector(dataPayload, 0x02))
|
||||
val metadata = """
|
||||
FILE "pregap.bin" BINARY
|
||||
TRACK 01 MODE1/2352
|
||||
INDEX 00 00:00:00
|
||||
INDEX 01 00:00:01
|
||||
""".trimIndent()
|
||||
|
||||
CdImageReader.openForFile(image, "pregap.cue", metadata.toByteArray()).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
|
||||
reader.readSector(1, buffer)
|
||||
|
||||
assertArrayEquals(dataPayload, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extractsCueAudioTrackAsWav() {
|
||||
val dataPayload = sectorFilledWith(0x33)
|
||||
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { it.toByte() }
|
||||
val image = tmp.newFile("audio.bin")
|
||||
image.writeBytes(rawDataSector(dataPayload, 0) + audio)
|
||||
val metadata = """
|
||||
FILE "audio.bin" BINARY
|
||||
TRACK 01 MODE1/2352
|
||||
INDEX 01 00:00:00
|
||||
TRACK 02 AUDIO
|
||||
INDEX 01 00:00:01
|
||||
""".trimIndent()
|
||||
|
||||
CdImageReader.openForFile(image, "audio.cue", metadata.toByteArray()).use { reader ->
|
||||
val output = ByteArrayOutputStream()
|
||||
|
||||
assertEquals(listOf(2), reader.audioTracks())
|
||||
reader.extractAudioTrack(2, output)
|
||||
|
||||
val wav = output.toByteArray()
|
||||
assertEquals("RIFF", wav.decodeAscii(0, 4))
|
||||
assertEquals(RAW_AUDIO_SECTOR_SIZE + 36, wav.readLeInt(4))
|
||||
assertEquals("WAVE", wav.decodeAscii(8, 4))
|
||||
assertEquals("data", wav.decodeAscii(36, 4))
|
||||
assertEquals(RAW_AUDIO_SECTOR_SIZE, wav.readLeInt(40))
|
||||
assertEquals(44100, wav.readLeInt(24))
|
||||
assertEquals(2, wav.readLeShort(22))
|
||||
assertEquals(16, wav.readLeShort(34))
|
||||
assertArrayEquals(audio, wav.copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsUnsupportedCueMode() {
|
||||
val image = tmp.newFile("unsupported.bin")
|
||||
image.writeBytes(ByteArray(CdImageReader.ISO_SECTOR_SIZE))
|
||||
|
||||
assertInstallFailure(R.string.unsupported_cd_image) {
|
||||
CdImageReader.openForFile(image, "unsupported.cue", cue("MODE2/2352").toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ccdUsesMode0ForAudioAndMode1PayloadOffsetForData() {
|
||||
val dataPayload = sectorFilledWith(0x52)
|
||||
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (255 - it).toByte() }
|
||||
val image = tmp.newFile("disc.img")
|
||||
image.writeBytes(rawDataSector(dataPayload, 0x08) + audio)
|
||||
val metadata = """
|
||||
[TRACK 1]
|
||||
MODE=1
|
||||
INDEX 1=0
|
||||
[TRACK 2]
|
||||
MODE=0
|
||||
INDEX 1=1
|
||||
""".trimIndent()
|
||||
|
||||
CdImageReader.openForFile(image, "disc.ccd", metadata.toByteArray()).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
val output = ByteArrayOutputStream()
|
||||
|
||||
reader.readSector(0, buffer)
|
||||
reader.extractAudioTrack(2, output)
|
||||
|
||||
assertArrayEquals(dataPayload, buffer)
|
||||
assertEquals(listOf(2), reader.audioTracks())
|
||||
assertArrayEquals(audio, output.toByteArray().copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mdsMapsModeAAToDataAndModeA9ToAudio() {
|
||||
val dataPayload = sectorFilledWith(0x61)
|
||||
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (it * 3).toByte() }
|
||||
val image = tmp.newFile("disc.mdf")
|
||||
image.writeBytes(rawDataSector(dataPayload, 0x09) + audio)
|
||||
|
||||
CdImageReader.openForFile(image, "disc.mds", mds(track1Sectors = 1, track2Sectors = 1)).use { reader ->
|
||||
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
|
||||
val output = ByteArrayOutputStream()
|
||||
|
||||
reader.readSector(0, buffer)
|
||||
reader.extractAudioTrack(2, output)
|
||||
|
||||
assertArrayEquals(dataPayload, buffer)
|
||||
assertEquals(listOf(2), reader.audioTracks())
|
||||
assertArrayEquals(audio, output.toByteArray().copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsMdsWithoutMediaDescriptorSignature() {
|
||||
val image = tmp.newFile("bad.mdf")
|
||||
image.writeBytes(ByteArray(RAW_AUDIO_SECTOR_SIZE))
|
||||
|
||||
assertInstallFailure(R.string.unsupported_cd_image) {
|
||||
CdImageReader.openForFile(image, "bad.mds", ByteArray(0x70))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GameInstallerCdImageTest {
|
||||
@get:Rule
|
||||
val tmp = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun installsDataOnlyIso() = runBlocking {
|
||||
val root = tmp.newFolder("root")
|
||||
val iso = SyntheticIsoBuilder()
|
||||
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
|
||||
.addGameDataFile("TITLE.TXT;1", "title".toByteArray())
|
||||
.writeTo(tmp.newFile("game.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
val gameDir = GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
|
||||
|
||||
assertEquals(File(root, "0/GAMEDATA").canonicalFile, gameDir.canonicalFile)
|
||||
assertEquals("GAMEDATA", File(root, "0/${Launcher.GAMEDIR_FILE}").readText())
|
||||
assertEquals("ald", File(gameDir, "SA.ALD").readText())
|
||||
assertEquals("title", File(gameDir, "TITLE.TXT").readText())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun installsCueAudioTrackAndPlaylist() = runBlocking {
|
||||
val root = tmp.newFolder("root")
|
||||
val isoSector = SyntheticIsoBuilder()
|
||||
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
|
||||
.toByteArray()
|
||||
val dataSectors = isoSector.toList().chunked(CdImageReader.ISO_SECTOR_SIZE)
|
||||
.joinToByteArray { rawDataSector(it.toByteArray(), 0) }
|
||||
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (it and 0x7f).toByte() }
|
||||
val image = tmp.newFile("audio.bin")
|
||||
image.writeBytes(dataSectors + audio)
|
||||
val dataSectorCount = isoSector.size / CdImageReader.ISO_SECTOR_SIZE
|
||||
val metadata = """
|
||||
FILE "audio.bin" BINARY
|
||||
TRACK 01 MODE1/2352
|
||||
INDEX 01 00:00:00
|
||||
TRACK 02 AUDIO
|
||||
INDEX 01 ${dataSectorCount.toMsf()}
|
||||
""".trimIndent()
|
||||
|
||||
CdImageReader.openForFile(image, "audio.cue", metadata.toByteArray()).use { reader ->
|
||||
val gameDir = GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
|
||||
|
||||
val wav = File(gameDir, "cdda/track02.wav")
|
||||
assertTrue(wav.exists())
|
||||
assertEquals("\ncdda/track02.wav", File(gameDir, Launcher.PLAYLIST_FILE).readText())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsIsoWithoutGameDataDirectoryAndDeletesIncompleteInstall() = runBlocking {
|
||||
val root = tmp.newFolder("root")
|
||||
val iso = SyntheticIsoBuilder(includeGameData = false).writeTo(tmp.newFile("nogamedata.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
assertInstallFailure(R.string.cannot_find_game_data_directory) {
|
||||
runBlocking {
|
||||
GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(File(root, "0").exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsIsoWithoutAldAndDeletesIncompleteInstall() = runBlocking {
|
||||
val root = tmp.newFolder("root")
|
||||
val iso = SyntheticIsoBuilder()
|
||||
.addGameDataFile("README.TXT;1", "readme".toByteArray())
|
||||
.writeTo(tmp.newFile("noald.iso"))
|
||||
|
||||
CdImageReader.openForFile(iso).use { reader ->
|
||||
assertInstallFailure(R.string.cannot_find_ald) {
|
||||
runBlocking {
|
||||
GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(File(root, "0").exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveOutputPathRejectsTraversal() {
|
||||
val base = tmp.newFolder("out")
|
||||
|
||||
assertThrowsIOException {
|
||||
resolveOutputPath(base, "../evil")
|
||||
}
|
||||
assertThrowsIOException {
|
||||
resolveOutputPath(base, File(base.parentFile, "evil").absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SyntheticIsoBuilder(
|
||||
private val includeGameData: Boolean = true,
|
||||
) {
|
||||
private val primaryFiles = mutableMapOf<String, ByteArray>()
|
||||
private val jolietFiles = mutableMapOf<String, ByteArray>()
|
||||
|
||||
fun addGameDataFile(path: String, content: ByteArray): SyntheticIsoBuilder {
|
||||
primaryFiles[path] = content
|
||||
return this
|
||||
}
|
||||
|
||||
fun addJolietGameDataFile(path: String, content: ByteArray): SyntheticIsoBuilder {
|
||||
jolietFiles[path] = content
|
||||
return this
|
||||
}
|
||||
|
||||
fun writeTo(file: File): File {
|
||||
file.writeBytes(toByteArray())
|
||||
return file
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
val sectors = MutableList(80) { ByteArray(CdImageReader.ISO_SECTOR_SIZE) }
|
||||
var nextSector = 30
|
||||
val primaryTree = buildTree(primaryFiles, Charset.forName("Shift_JIS"))
|
||||
val primaryRoot = writeTree(sectors, primaryTree, ROOT_SECTOR, GAMEDATA_SECTOR, nextSector, false)
|
||||
nextSector = primaryRoot.nextSector
|
||||
|
||||
writeVolumeDescriptor(sectors[16], 1, ROOT_SECTOR, CdImageReader.ISO_SECTOR_SIZE, false)
|
||||
var terminatorSector = 17
|
||||
if (jolietFiles.isNotEmpty()) {
|
||||
val jolietTree = buildTree(jolietFiles, Charsets.UTF_16BE)
|
||||
writeTree(sectors, jolietTree, JOLIET_ROOT_SECTOR, JOLIET_GAMEDATA_SECTOR, nextSector, true)
|
||||
writeVolumeDescriptor(sectors[17], 2, JOLIET_ROOT_SECTOR, CdImageReader.ISO_SECTOR_SIZE, true)
|
||||
terminatorSector = 18
|
||||
}
|
||||
writeTerminator(sectors[terminatorSector])
|
||||
return sectors.flattenToByteArray()
|
||||
}
|
||||
|
||||
private fun buildTree(files: Map<String, ByteArray>, charset: Charset): DirectoryNode {
|
||||
val root = DirectoryNode("")
|
||||
if (!includeGameData) {
|
||||
return root
|
||||
}
|
||||
val gameData = DirectoryNode("GAMEDATA")
|
||||
root.directories["GAMEDATA"] = gameData
|
||||
for ((path, content) in files) {
|
||||
var directory = gameData
|
||||
val parts = path.split("/")
|
||||
for (part in parts.dropLast(1)) {
|
||||
directory = directory.directories.getOrPut(part.substringBefore(";")) {
|
||||
DirectoryNode(part.substringBefore(";"))
|
||||
}
|
||||
}
|
||||
val rawName = parts.last().toByteArray(charset)
|
||||
directory.files.add(FileNode(rawName, content))
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
private fun writeTree(
|
||||
sectors: MutableList<ByteArray>,
|
||||
root: DirectoryNode,
|
||||
rootSector: Int,
|
||||
gameDataSector: Int,
|
||||
firstFileSector: Int,
|
||||
joliet: Boolean,
|
||||
): TreeWriteResult {
|
||||
var nextSector = firstFileSector
|
||||
root.extent = rootSector
|
||||
root.size = CdImageReader.ISO_SECTOR_SIZE
|
||||
if (includeGameData) {
|
||||
root.directories["GAMEDATA"]!!.extent = gameDataSector
|
||||
root.directories["GAMEDATA"]!!.size = CdImageReader.ISO_SECTOR_SIZE
|
||||
}
|
||||
assignDirectorySectors(root, gameDataSector + 1)
|
||||
nextSector = writeFileContents(sectors, root, nextSector)
|
||||
writeDirectorySector(sectors[rootSector], root, root, joliet)
|
||||
if (includeGameData) {
|
||||
writeDirectorySectors(sectors, root.directories["GAMEDATA"]!!, root, joliet)
|
||||
}
|
||||
return TreeWriteResult(nextSector)
|
||||
}
|
||||
|
||||
private fun assignDirectorySectors(directory: DirectoryNode, nextDirectorySector: Int): Int {
|
||||
var sector = nextDirectorySector
|
||||
for (child in directory.directories.values) {
|
||||
if (child.name == "GAMEDATA") {
|
||||
sector = assignDirectorySectors(child, sector)
|
||||
continue
|
||||
}
|
||||
child.extent = sector++
|
||||
child.size = CdImageReader.ISO_SECTOR_SIZE
|
||||
sector = assignDirectorySectors(child, sector)
|
||||
}
|
||||
return sector
|
||||
}
|
||||
|
||||
private fun writeFileContents(
|
||||
sectors: MutableList<ByteArray>,
|
||||
directory: DirectoryNode,
|
||||
firstFileSector: Int,
|
||||
): Int {
|
||||
var sector = firstFileSector
|
||||
for (child in directory.directories.values) {
|
||||
sector = writeFileContents(sectors, child, sector)
|
||||
}
|
||||
for (file in directory.files) {
|
||||
file.extent = sector
|
||||
file.size = file.content.size
|
||||
val sectorCount = (file.content.size + CdImageReader.ISO_SECTOR_SIZE - 1) /
|
||||
CdImageReader.ISO_SECTOR_SIZE
|
||||
for (i in 0 until sectorCount) {
|
||||
file.content.copyInto(
|
||||
sectors[sector + i],
|
||||
0,
|
||||
i * CdImageReader.ISO_SECTOR_SIZE,
|
||||
minOf(file.content.size, (i + 1) * CdImageReader.ISO_SECTOR_SIZE)
|
||||
)
|
||||
}
|
||||
sector += maxOf(1, sectorCount)
|
||||
}
|
||||
return sector
|
||||
}
|
||||
|
||||
private fun writeDirectorySectors(
|
||||
sectors: MutableList<ByteArray>,
|
||||
directory: DirectoryNode,
|
||||
parent: DirectoryNode,
|
||||
joliet: Boolean,
|
||||
) {
|
||||
writeDirectorySector(sectors[directory.extent], directory, parent, joliet)
|
||||
for (child in directory.directories.values) {
|
||||
writeDirectorySectors(sectors, child, directory, joliet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeDirectorySector(
|
||||
sector: ByteArray,
|
||||
directory: DirectoryNode,
|
||||
parent: DirectoryNode,
|
||||
joliet: Boolean,
|
||||
) {
|
||||
var offset = 0
|
||||
offset += writeRecord(sector, offset, byteArrayOf(0), directory.extent, directory.size, true)
|
||||
offset += writeRecord(sector, offset, byteArrayOf(1), parent.extent, parent.size, true)
|
||||
for (child in directory.directories.values) {
|
||||
val name = if (joliet) child.name.toByteArray(Charsets.UTF_16BE) else child.name.toByteArray()
|
||||
offset += writeRecord(sector, offset, name, child.extent, child.size, true)
|
||||
}
|
||||
for (file in directory.files) {
|
||||
offset += writeRecord(sector, offset, file.rawName, file.extent, file.size, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeVolumeDescriptor(
|
||||
sector: ByteArray,
|
||||
type: Int,
|
||||
rootExtent: Int,
|
||||
rootSize: Int,
|
||||
joliet: Boolean,
|
||||
) {
|
||||
sector[0] = type.toByte()
|
||||
"CD001".toByteArray(Charsets.US_ASCII).copyInto(sector, 1)
|
||||
sector[6] = 1
|
||||
if (joliet) {
|
||||
sector[88] = 0x25
|
||||
sector[89] = 0x2f
|
||||
sector[90] = 0x45
|
||||
}
|
||||
writeRecord(sector, 156, byteArrayOf(0), rootExtent, rootSize, true)
|
||||
}
|
||||
|
||||
private fun writeTerminator(sector: ByteArray) {
|
||||
sector[0] = 255.toByte()
|
||||
"CD001".toByteArray(Charsets.US_ASCII).copyInto(sector, 1)
|
||||
sector[6] = 1
|
||||
}
|
||||
|
||||
private fun writeRecord(
|
||||
buffer: ByteArray,
|
||||
offset: Int,
|
||||
rawName: ByteArray,
|
||||
extent: Int,
|
||||
size: Int,
|
||||
isDirectory: Boolean,
|
||||
): Int {
|
||||
val length = 33 + rawName.size + if (rawName.size % 2 == 0) 1 else 0
|
||||
buffer[offset] = length.toByte()
|
||||
buffer.writeLeInt(offset + 2, extent)
|
||||
buffer.writeLeInt(offset + 10, size)
|
||||
buffer[offset + 25] = if (isDirectory) 0x02 else 0x00
|
||||
buffer[offset + 32] = rawName.size.toByte()
|
||||
rawName.copyInto(buffer, offset + 33)
|
||||
return length
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ROOT_SECTOR = 20
|
||||
private const val GAMEDATA_SECTOR = 21
|
||||
private const val JOLIET_ROOT_SECTOR = 24
|
||||
private const val JOLIET_GAMEDATA_SECTOR = 25
|
||||
}
|
||||
}
|
||||
|
||||
private data class DirectoryNode(
|
||||
val name: String,
|
||||
val directories: MutableMap<String, DirectoryNode> = linkedMapOf(),
|
||||
val files: MutableList<FileNode> = mutableListOf(),
|
||||
var extent: Int = 0,
|
||||
var size: Int = 0,
|
||||
)
|
||||
|
||||
private data class FileNode(
|
||||
val rawName: ByteArray,
|
||||
val content: ByteArray,
|
||||
var extent: Int = 0,
|
||||
var size: Int = 0,
|
||||
)
|
||||
|
||||
private data class TreeWriteResult(val nextSector: Int)
|
||||
|
||||
private const val RAW_AUDIO_SECTOR_SIZE = 2352
|
||||
|
||||
private fun cue(mode: String): String = """
|
||||
FILE "image.bin" BINARY
|
||||
TRACK 01 $mode
|
||||
INDEX 01 00:00:00
|
||||
""".trimIndent()
|
||||
|
||||
private fun mds(track1Sectors: Int, track2Sectors: Int): ByteArray {
|
||||
val bytes = ByteArray(0x70 + 2 * 0x50 + 2 * 8)
|
||||
"MEDIA DESCRIPTOR".toByteArray(Charsets.US_ASCII).copyInto(bytes, 0)
|
||||
bytes[0x62] = 2
|
||||
val track1 = 0x70
|
||||
bytes[track1] = 0xaa.toByte()
|
||||
bytes[track1 + 0x04] = 1
|
||||
bytes.writeLeShort(track1 + 0x10, RAW_AUDIO_SECTOR_SIZE)
|
||||
bytes.writeLeInt(track1 + 0x28, 0)
|
||||
bytes.writeLeInt(0x70 + 2 * 0x50 + 0x04, track1Sectors)
|
||||
val track2 = 0x70 + 0x50
|
||||
bytes[track2] = 0xa9.toByte()
|
||||
bytes[track2 + 0x04] = 2
|
||||
bytes.writeLeShort(track2 + 0x10, RAW_AUDIO_SECTOR_SIZE)
|
||||
bytes.writeLeInt(track2 + 0x28, track1Sectors * RAW_AUDIO_SECTOR_SIZE)
|
||||
bytes.writeLeInt(0x70 + 2 * 0x50 + 8 + 0x04, track2Sectors)
|
||||
return bytes
|
||||
}
|
||||
|
||||
private fun sectorFilledWith(value: Int): ByteArray {
|
||||
return ByteArray(CdImageReader.ISO_SECTOR_SIZE) { value.toByte() }
|
||||
}
|
||||
|
||||
private fun rawDataSector(payload: ByteArray, prefixValue: Int): ByteArray {
|
||||
val sector = ByteArray(RAW_AUDIO_SECTOR_SIZE) { prefixValue.toByte() }
|
||||
payload.copyInto(sector, 16, 0, CdImageReader.ISO_SECTOR_SIZE)
|
||||
return sector
|
||||
}
|
||||
|
||||
private fun Int.toMsf(): String {
|
||||
val minutes = this / (60 * 75)
|
||||
val seconds = this / 75 % 60
|
||||
val frames = this % 75
|
||||
return "%02d:%02d:%02d".format(minutes, seconds, frames)
|
||||
}
|
||||
|
||||
private fun Iterable<Byte>.toByteArray(): ByteArray {
|
||||
val result = ByteArray(count())
|
||||
forEachIndexed { index, byte -> result[index] = byte }
|
||||
return result
|
||||
}
|
||||
|
||||
private fun List<ByteArray>.flattenToByteArray(): ByteArray {
|
||||
val result = ByteArray(sumOf { it.size })
|
||||
var offset = 0
|
||||
for (bytes in this) {
|
||||
bytes.copyInto(result, offset)
|
||||
offset += bytes.size
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun List<List<Byte>>.joinToByteArray(transform: (List<Byte>) -> ByteArray): ByteArray {
|
||||
return map(transform).flattenToByteArray()
|
||||
}
|
||||
|
||||
private fun ByteArray.decodeAscii(offset: Int, length: Int): String {
|
||||
return String(this, offset, length, Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
private fun ByteArray.readLeShort(offset: Int): Int {
|
||||
return (this[offset].toInt() and 0xff) or ((this[offset + 1].toInt() and 0xff) shl 8)
|
||||
}
|
||||
|
||||
private fun ByteArray.readLeInt(offset: Int): Int {
|
||||
return readLeShort(offset) or (readLeShort(offset + 2) shl 16)
|
||||
}
|
||||
|
||||
private fun ByteArray.writeLeShort(offset: Int, value: Int) {
|
||||
this[offset] = value.toByte()
|
||||
this[offset + 1] = (value shr 8).toByte()
|
||||
}
|
||||
|
||||
private fun ByteArray.writeLeInt(offset: Int, value: Int) {
|
||||
writeLeShort(offset, value)
|
||||
writeLeShort(offset + 2, value shr 16)
|
||||
}
|
||||
|
||||
private fun assertInstallFailure(expectedMsgId: Int, block: () -> Unit): InstallFailureException {
|
||||
try {
|
||||
block()
|
||||
} catch (e: InstallFailureException) {
|
||||
assertEquals(expectedMsgId, e.msgId)
|
||||
return e
|
||||
}
|
||||
fail("Expected InstallFailureException")
|
||||
throw AssertionError()
|
||||
}
|
||||
|
||||
private fun assertThrowsIOException(block: () -> Unit) {
|
||||
try {
|
||||
block()
|
||||
} catch (e: java.io.IOException) {
|
||||
return
|
||||
}
|
||||
fail("Expected IOException")
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.72'
|
||||
ext.kotlin_version = '1.7.21'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.android.tools.build:gradle:8.7.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
BIN
Binary file not shown.
+3
-3
@@ -1,6 +1,6 @@
|
||||
#Mon Aug 10 12:24:31 JST 2020
|
||||
#Thu Nov 11 18:20:34 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+2
-2
@@ -126,8 +126,8 @@ if $cygwin ; then
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
CHECK=`echo "$arg"|grep -E -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|grep -E -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
|
||||
+4
-24
@@ -1,38 +1,18 @@
|
||||
#cmakedefine PACKAGE "@PACKAGE@"
|
||||
#define VERSION "2.1.0"
|
||||
#define VERSION "@XSYSTEM35_VERSION@"
|
||||
|
||||
#cmakedefine CMAKE_SYSTEM_NAME "@CMAKE_SYSTEM_NAME@"
|
||||
|
||||
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
|
||||
|
||||
#cmakedefine ENABLE_CDROM_ANDROID @ENABLE_CDROM_ANDROID@
|
||||
#cmakedefine ENABLE_CDROM_BSD @ENABLE_CDROM_BSD@
|
||||
#cmakedefine ENABLE_CDROM_EMSCRIPTEN @ENABLE_CDROM_EMSCRIPTEN@
|
||||
#cmakedefine ENABLE_CDROM_LINUX @ENABLE_CDROM_LINUX@
|
||||
#cmakedefine ENABLE_CDROM_MP3 @ENABLE_CDROM_MP3@
|
||||
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
|
||||
#cmakedefine ENABLE_GTK @ENABLE_GTK@
|
||||
#cmakedefine ENABLE_MIDI_RAWMIDI @ENABLE_MIDI_RAWMIDI@
|
||||
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
|
||||
#cmakedefine ENABLE_MIDI_SEQMIDI @ENABLE_MIDI_SEQMIDI@
|
||||
#cmakedefine ENABLE_MIDI_PORTMIDI @ENABLE_MIDI_PORTMIDI@
|
||||
#cmakedefine ENABLE_NLS @ENABLE_NLS@
|
||||
#cmakedefine ENABLE_SDLMIXER @ENABLE_SDLMIXER@
|
||||
|
||||
#cmakedefine CDROM_DEVICE "@CDROM_DEVICE@"
|
||||
#cmakedefine DEFAULT_PLAYLIST_PATH "@DEFAULT_PLAYLIST_PATH@"
|
||||
#cmakedefine JOY_DEVICE "@JOY_DEVICE@"
|
||||
#cmakedefine MIDI_DEVICE "@MIDI_DEVICE@"
|
||||
#cmakedefine SEQ_DEVICE "@SEQ_DEVICE@"
|
||||
|
||||
#cmakedefine QUIET_MIDI @QUIET_MIDI@
|
||||
|
||||
#cmakedefine HAVE_LIBINTL @HAVE_LIBINTL@
|
||||
#cmakedefine HAVE_GETLOGIN @HAVE_GETLOGIN@
|
||||
#cmakedefine HAVE_JPEG @HAVE_JPEG@
|
||||
#cmakedefine HAVE_MMAP @HAVE_MMAP@
|
||||
#cmakedefine HAVE_SDLJOY @HAVE_SDLJOY@
|
||||
#cmakedefine HAVE_SIGACTION @HAVE_SIGACTION@
|
||||
#cmakedefine HAVE_UNAME @HAVE_UNAME@
|
||||
#cmakedefine HAVE_WEBP @HAVE_WEBP@
|
||||
|
||||
#define DEFAULT_GOTHIC_TTF "@DEFAULT_FONT_PATH@MTLc3m.ttf"
|
||||
#define DEFAULT_MINCHO_TTF "@DEFAULT_FONT_PATH@mincho.otf"
|
||||
#define DEFAULT_MINCHO_TTF "@DEFAULT_FONT_PATH@mincho.ttf"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# 20世紀アリス これDPS? Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, 20cDPS_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, 20cDPS_ga.ald
|
||||
MidiA, gamedata/*MA.ald, 20cDPS_ma.ald
|
||||
SaveA, , 20cDPS_sa.asd
|
||||
Ain, gamedata/*.ain, system39.ain
|
||||
@@ -1,10 +0,0 @@
|
||||
# 20世紀アリス かえるにょ国にょアリス Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, 20cKaeru_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, 20cKaeru_ga.ald
|
||||
WaveA, gamedata/*WA.ald, 20cKaeru_wa.ald
|
||||
MidiA, gamedata/*MA.ald, 20cKaeru_ma.ald
|
||||
DataA, gamedata/*DA.ald, 20cKaeru_da.ald
|
||||
Ain, gamedata/*.ain, system39.ain
|
||||
SaveA, , 20cKaeru_sa.asd
|
||||
SaveZ, , 20cKaeru_sz.asd
|
||||
@@ -1,14 +0,0 @@
|
||||
# ATLACH-NACHA Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, atlach_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, atlach_ga.ald
|
||||
GraphicsB, gamedata/*gb.ald, atlach_gb.ald
|
||||
WaveA, gamedata/*wa.ald, atlach_wa.ald
|
||||
SaveA, , atlach_sa.asd
|
||||
SaveB, , atlach_sb.asd
|
||||
SaveC, , atlach_sc.asd
|
||||
SaveD, , atlach_sd.asd
|
||||
SaveE, , atlach_se.asd
|
||||
SaveF, , atlach_sf.asd
|
||||
SaveG, , atlach_sg.asd
|
||||
SaveH, , atlach_sh.asd
|
||||
@@ -1,6 +0,0 @@
|
||||
# Ningen Kari Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, ningen_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, ningen_ga.ald
|
||||
WaveA, gamedata/*wa.ald, ningen_wa.ald
|
||||
SaveA, , ningen_sa.asd
|
||||
@@ -1,12 +0,0 @@
|
||||
# Zero-shiki Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, zero_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, zero_ga.ald
|
||||
WaveA, gamedata/*wa.ald, zero_wa.ald
|
||||
SaveA, , zero_sa.asd
|
||||
SaveB, , zero_sb.asd
|
||||
SaveC, , zero_sc.asd
|
||||
SaveD, , zero_sd.asd
|
||||
SaveE, , zero_se.asd
|
||||
, gamedata/map_data.dat, MAP_DATA.DAT
|
||||
, gamedata/ur_data.dat, UR_DATA.DAT
|
||||
@@ -1,115 +0,0 @@
|
||||
ゲームデータインストール用スクリプト instgame の使い方について
|
||||
|
||||
スクリプト作成 奈良木 さん
|
||||
ドキュメント作成 近間@NAIST
|
||||
0. 目次
|
||||
|
||||
1. ファイルの説明
|
||||
2. 使い方
|
||||
3. その他の情報
|
||||
4. 著作権など
|
||||
5. 連絡先
|
||||
|
||||
|
||||
1. ファイルの説明
|
||||
|
||||
instgame スクリプト本体
|
||||
456atlach.inf ATLACH-NACHA 用設定ファイル (アリスの館456収録)
|
||||
456ningen.inf 人間狩り 用設定ファイル (アリスの館456収録
|
||||
456zero.inf 零式 用設定ファイル (アリスの館456収録)
|
||||
ambi.inf AmbivalenZ for WIN95 用設定ファイル
|
||||
atlach_new.inf ATLACH-NACHA (廉価版) 用設定ファイル
|
||||
darcrows.inf DARCROWS 用設定ファイル
|
||||
diabo.inf DiaboLiQue 用設定ファイル
|
||||
diabo_new.inf DiaboLiQue (廉価版) 用設定ファイル
|
||||
hushaby.inf HushaBy Baby 用設定ファイル
|
||||
katsumi.inf いけないかつみ先生 用設定ファイル
|
||||
kichiku.inf 鬼畜王ランス 用設定ファイル
|
||||
mamatoto.inf ママトト 用設定ファイル
|
||||
mamori.inf 守り神様 用設定ファイル
|
||||
miko.inf 戦巫女 用設定ファイル
|
||||
mugen.inf 無幻泡影 for WIN95 用設定ファイル
|
||||
oudou.inf 王道勇者 用設定ファイル
|
||||
panyo.inf かえるにょぱにょ〜ん 用設定ファイル
|
||||
panyo_new.inf かえるにょぱにょ〜ん(廉価版) 用設定ファイル
|
||||
pascha.inf ぱすてるチャイム 用設定ファイル
|
||||
persiom.inf PERSIOM 用設定ファイル
|
||||
progood.inf ぷろすちゅーでんとGOOD 用設定ファイル
|
||||
rance4.inf ランス4 forWIN 用設定ファイル
|
||||
seeinao.inf SeeIn青 用設定ファイル
|
||||
toushin2.inf 闘神都市II forWIN 用設定ファイル
|
||||
dalk.inf DALK 用設定ファイル (アリスの館456収録)
|
||||
drstop.inf Dr.Stop! 用設定ファイル (アリスの館456収録)
|
||||
rance1.inf Rance1 用設定ファイル (アリスの館456収録)
|
||||
rance2.inf Rance2 用設定ファイル (アリスの館456収録)
|
||||
rance3.inf Rance3 用設定ファイル (アリスの館456収録)
|
||||
toushin.inf 闘神都市 用設定ファイル (アリスの館456収録)
|
||||
zero_new.inf 零式 (廉価版) 用設定ファイル
|
||||
20cKaeru.inf 20世紀アリス かえるにょ国にょアリス 用設定ファイル
|
||||
20cDPS.inf 20世紀アリス これDPS? 用設定ファイル
|
||||
onlyyou_r.inf Only You リクルス 用設定ファイル
|
||||
daiakuji.inf 大悪司 用設定ファイル
|
||||
tsuma.inf 妻みぐい 用設定ファイル
|
||||
|
||||
2. 使い方
|
||||
|
||||
ゲームのCD-ROMをドライブに入れてマウントし、
|
||||
% ./instgame kichiku.inf
|
||||
のように実行します。
|
||||
|
||||
デフォルトではCDROMは/cdromにマウントされているものとしています。
|
||||
また、データのインストール先は ~/game/kichiku/ の下になります。
|
||||
|
||||
デフォルトでは設定用ファイルのファイル名が~/game/の下のディレクトリ名に
|
||||
なります。
|
||||
|
||||
これらのデフォルトの条件を変えるには以下のオプションを使います。
|
||||
|
||||
-c : CD-ROMのマウントされている位置を指定します(default: -c /cdrom)
|
||||
-d : ゲームデータのインストール先を指定します (default: -d ~/game/target>)
|
||||
|
||||
データが正常にコピーされると、~/game/の下にkichiku.grと言うファイルが作成
|
||||
されます。これはゲームデータの場所をプログラム(xsystem35)に知らせるためのも
|
||||
ので、
|
||||
% xsystem35 -gamefile ~/game/kichiku.gr
|
||||
のように指定するのに使います。(「ゲームリソースファイル」とでも呼んで下さい)
|
||||
|
||||
デフォルトでは、*.grというファイルも設定用ファイルのファイル名と同じ名前に
|
||||
なりますが、以下のオプションで変更可能です。
|
||||
|
||||
-g : ゲームリソースファイルのファイル名を指定します
|
||||
(default: -g ~/game/<target>.inf)
|
||||
|
||||
|
||||
3. その他の情報
|
||||
|
||||
その他のオプションに、指定のファイルはCD-ROMからコピーせずにリンクを張る、
|
||||
という指定が出来ます。
|
||||
|
||||
% ./instgame -k Graphics kichiku.inf
|
||||
|
||||
とすると、kichiku_ga.ald と kichiku_gb.ald のファイルはコピーされずに、
|
||||
CD-ROM上のデータへのシンボリックリンクになります。ただし、そうするとゲーム中
|
||||
はCD-ROMをマウントしておかなければならないので、CD-ROMをマウントしたまま
|
||||
CD-DAの演奏可能な一部のシステムでのみ可能です。
|
||||
|
||||
|
||||
このオプションはゲームデータのインストール時の動作を規定するものなので、
|
||||
自分でCD-ROMからファイルをコピーして、ゲームリソースファイルをエディタで
|
||||
編集すれば、全く自由にデータの配置を行なう事が出来ます。もちろん、ゲーム
|
||||
リソースファイルのファイル名や置き場所も自由にして出来ます。
|
||||
ゲームリソースファイルの書式については doc/GRFMT.TXT を御覧下さい。
|
||||
|
||||
|
||||
4. 著作権など
|
||||
|
||||
instgameは 奈良木 さんがつくられたものですので、著作権は奈良木さんに
|
||||
属します。奈良木さんの意向により、GPLに基づいて再配布可能です。
|
||||
|
||||
ドキュメントの不備は私(近間)までどうぞ。
|
||||
|
||||
|
||||
5. 連絡先
|
||||
|
||||
奈良木さん <naraki@debian.or.jp>
|
||||
近間@NAIST <masaki-c@is.aist-nara.ac.jp>
|
||||
@@ -1,18 +0,0 @@
|
||||
# AmbivalenZ Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, ambi_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, ambi_ga.ald
|
||||
SaveA, , ambi_sa.asd
|
||||
SaveB, , ambi_sb.asd
|
||||
SaveC, , ambi_sc.asd
|
||||
SaveD, , ambi_sd.asd
|
||||
SaveE, , ambi_se.asd
|
||||
SaveF, , ambi_sf.asd
|
||||
SaveG, , ambi_sg.asd
|
||||
SaveH, , ambi_sh.asd
|
||||
SaveI, , ambi_si.asd
|
||||
SaveJ, , ambi_sj.asd
|
||||
SaveK, , ambi_sk.asd
|
||||
SaveL, , ambi_sl.asd
|
||||
SaveM, , ambi_sm.asd
|
||||
SaveN, , ambi_sn.asd
|
||||
@@ -1,30 +0,0 @@
|
||||
# ATLACH-NACHA (low price version) Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, atlach_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, atlach_ga.ald
|
||||
WaveA, gamedata/*wa.ald, atlach_wa.ald
|
||||
SaveA, , atlach_sa.asd
|
||||
SaveB, , atlach_sb.asd
|
||||
SaveC, , atlach_sc.asd
|
||||
SaveD, , atlach_sd.asd
|
||||
SaveE, , atlach_se.asd
|
||||
SaveF, , atlach_sf.asd
|
||||
SaveG, , atlach_sg.asd
|
||||
SaveH, , atlach_sh.asd
|
||||
SaveI, , atlach_si.asd
|
||||
SaveJ, , atlach_sj.asd
|
||||
SaveK, , atlach_sk.asd
|
||||
SaveL, , atlach_sl.asd
|
||||
SaveM, , atlach_sm.asd
|
||||
SaveN, , atlach_sn.asd
|
||||
SaveO, , atlach_so.asd
|
||||
SaveP, , atlach_sp.asd
|
||||
SaveQ, , atlach_sq.asd
|
||||
SaveR, , atlach_sr.asd
|
||||
SaveS, , atlach_ss.asd
|
||||
SaveT, , atlach_st.asd
|
||||
SaveU, , atlach_su.asd
|
||||
SaveV, , atlach_sv.asd
|
||||
SaveW, , atlach_sw.asd
|
||||
SaveX, , atlach_sx.asd
|
||||
SaveY, , atlach_sy.asd
|
||||
@@ -1,37 +0,0 @@
|
||||
# シェル・クレイル Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, chairs_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ald, chairs_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ald, chairs_wa.ald
|
||||
BGMA, GAMEDATA/*BA.ald, chairs_ba.ald
|
||||
WAIA, GAMEDATA/*WA.wai, chairs_wa.wai
|
||||
BGIA, GAMEDATA/*BA.bgi, chairs_ba.bgi
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
Init, GAMEDATA/*.ini, system39.ini
|
||||
SACT01, GAMEDATA/SACTEFAM.KLD, SACTEFAM.KLD
|
||||
SaveA, , chairs_sa.asd
|
||||
SaveB, , chairs_sb.asd
|
||||
SaveC, , chairs_sc.asd
|
||||
SaveD, , chairs_sd.asd
|
||||
SaveE, , chairs_se.asd
|
||||
SaveF, , chairs_sf.asd
|
||||
SaveG, , chairs_sg.asd
|
||||
SaveH, , chairs_sh.asd
|
||||
SaveI, , chairs_si.asd
|
||||
SaveJ, , chairs_sj.asd
|
||||
SaveK, , chairs_sk.asd
|
||||
SaveL, , chairs_sl.asd
|
||||
SaveM, , chairs_sm.asd
|
||||
SaveN, , chairs_sn.asd
|
||||
SaveO, , chairs_so.asd
|
||||
SaveP, , chairs_sp.asd
|
||||
SaveQ, , chairs_sq.asd
|
||||
SaveR, , chairs_sr.asd
|
||||
SaveS, , chairs_ss.asd
|
||||
SaveT, , chairs_st.asd
|
||||
SaveU, , chairs_su.asd
|
||||
SaveV, , chairs_sv.asd
|
||||
SaveW, , chairs_sw.asd
|
||||
SaveX, , chairs_sx.asd
|
||||
SaveY, , chairs_sy.asd
|
||||
SaveZ, , chairs_sz.asd
|
||||
@@ -1,19 +0,0 @@
|
||||
# 大悪司 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, daiakuji_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, daiakuji_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, daiakuji_wa.ald
|
||||
DataA, GAMEDATA/*DA.ALD, daiakuji_da.ald
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
SaveA, , daiakuji_sa.asd
|
||||
SaveB, , daiakuji_sb.asd
|
||||
SaveC, , daiakuji_sc.asd
|
||||
SaveD, , daiakuji_sd.asd
|
||||
SaveE, , daiakuji_se.asd
|
||||
SaveF, , daiakuji_sf.asd
|
||||
SaveG, , daiakuji_sg.asd
|
||||
SaveH, , daiakuji_sh.asd
|
||||
SaveI, , daiakuji_si.asd
|
||||
SaveJ, , daiakuji_sj.asd
|
||||
SaveK, , daiakuji_sk.asd
|
||||
SaveL, , daiakuji_sl.asd
|
||||
@@ -1,17 +0,0 @@
|
||||
# dalk Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, dalk/dalk_sa.ald, dalk_sa.ald
|
||||
GraphicsA, dalk/dalk_ga.ald, dalk_ga.ald
|
||||
MidiA, dalk/dalk_ma.ald, dalk_ma.ald
|
||||
WaveA, dalk/dalk_wa.ald, dalk_wa.ald
|
||||
DataA, dalk/dalk_da.ald, dalk_da.ald
|
||||
SaveA, , dalk_sa.asd
|
||||
SaveB, , dalk_sb.asd
|
||||
SaveC, , dalk_sc.asd
|
||||
SaveD, , dalk_sd.asd
|
||||
SaveE, , dalk_se.asd
|
||||
SaveF, , dalk_sf.asd
|
||||
SaveG, , dalk_sg.asd
|
||||
SaveH, , dalk_sh.asd
|
||||
SaveI, , dalk_si.asd
|
||||
SaveJ, , dalk_sj.asd
|
||||
@@ -1,35 +0,0 @@
|
||||
# DARCROWS Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ALD, hime_sa.ald
|
||||
GraphicsA, gamedata/*GA.ALD, hime_ga.ald
|
||||
GraphicsB, gamedata/*GB.ALD, hime_gb.ald
|
||||
WaveA, gamedata/*WA.ALD, hime_wa.ald
|
||||
WaveB, gamedata/*WB.ALD, hime_wb.ald
|
||||
MidiA, gamedata/*MA.ALD, hime_ma.ald
|
||||
ResourceA, gamedata/*RA.ALD, hime_ra.ald
|
||||
SaveA, , hime_sa.asd
|
||||
SaveB, , hime_sb.asd
|
||||
SaveC, , hime_sc.asd
|
||||
SaveD, , hime_sd.asd
|
||||
SaveE, , hime_se.asd
|
||||
SaveF, , hime_sf.asd
|
||||
SaveG, , hime_sg.asd
|
||||
SaveH, , hime_sh.asd
|
||||
SaveI, , hime_si.asd
|
||||
SaveJ, , hime_sj.asd
|
||||
SaveK, , hime_sk.asd
|
||||
SaveL, , hime_sl.asd
|
||||
SaveM, , hime_sm.asd
|
||||
SaveN, , hime_sn.asd
|
||||
SaveO, , hime_so.asd
|
||||
SaveP, , hime_sp.asd
|
||||
SaveQ, , hime_sq.asd
|
||||
SaveR, , hime_sr.asd
|
||||
SaveS, , hime_ss.asd
|
||||
SaveT, , hime_st.asd
|
||||
SaveU, , hime_su.asd
|
||||
SaveV, , hime_sv.asd
|
||||
SaveW, , hime_sw.asd
|
||||
SaveX, , hime_sx.asd
|
||||
SaveY, , hime_sy.asd
|
||||
SaveZ, , hime_sz.asd
|
||||
@@ -1,16 +0,0 @@
|
||||
# DiaboLiQue Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, diabo_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, diabo_ga.ald
|
||||
GraphicsB, gamedata/*gb.ald, diabo_gb.ald
|
||||
WaveA, gamedata/*wa.ald, diabo_wa.ald
|
||||
SaveA, , diabo_sa.asd
|
||||
SaveB, , diabo_sb.asd
|
||||
SaveC, , diabo_sc.asd
|
||||
SaveD, , diabo_sd.asd
|
||||
SaveE, , diabo_se.asd
|
||||
SaveF, , diabo_sf.asd
|
||||
SaveG, , diabo_sg.asd
|
||||
SaveH, , diabo_sh.asd
|
||||
SaveI, , diabo_si.asd
|
||||
SaveJ, , diabo_sj.asd
|
||||
@@ -1,30 +0,0 @@
|
||||
# DiaboLiQue (low price version) Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, diabo_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, diabo_ga.ald
|
||||
WaveA, gamedata/*wa.ald, diabo_wa.ald
|
||||
SaveA, , diabo_sa.asd
|
||||
SaveB, , diabo_sb.asd
|
||||
SaveC, , diabo_sc.asd
|
||||
SaveD, , diabo_sd.asd
|
||||
SaveE, , diabo_se.asd
|
||||
SaveF, , diabo_sf.asd
|
||||
SaveG, , diabo_sg.asd
|
||||
SaveH, , diabo_sh.asd
|
||||
SaveI, , diabo_si.asd
|
||||
SaveJ, , diabo_sj.asd
|
||||
SaveK, , diabo_sk.asd
|
||||
SaveL, , diabo_sl.asd
|
||||
SaveM, , diabo_sm.asd
|
||||
SaveN, , diabo_sn.asd
|
||||
SaveO, , diabo_so.asd
|
||||
SaveP, , diabo_sp.asd
|
||||
SaveQ, , diabo_sq.asd
|
||||
SaveR, , diabo_sr.asd
|
||||
SaveS, , diabo_ss.asd
|
||||
SaveT, , diabo_st.asd
|
||||
SaveU, , diabo_su.asd
|
||||
SaveV, , diabo_sv.asd
|
||||
SaveW, , diabo_sw.asd
|
||||
SaveX, , diabo_sx.asd
|
||||
SaveY, , diabo_sy.asd
|
||||
@@ -1,10 +0,0 @@
|
||||
# DrStop! Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, drstop\!/ds_sa.ald, ds_sa.ald
|
||||
GraphicsA, drstop\!/ds_ga.ald, ds_ga.ald
|
||||
MidiA, drstop\!/ds_ma.ald, ds_ma.ald
|
||||
SaveA, , ds_sa.asd
|
||||
SaveB, , ds_sb.asd
|
||||
SaveC, , ds_sc.asd
|
||||
SaveD, , ds_sd.asd
|
||||
SaveE, , ds_se.asd
|
||||
@@ -1,38 +0,0 @@
|
||||
# エスカレイヤー Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, esuka_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, esuka_ga.ald
|
||||
GraphicsB, GAMEDATA/*GB.ALD, esuka_gb.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, esuka_wa.ald
|
||||
WaveB, GAMEDATA/*WB.ALD, esuka_wb.ald
|
||||
BGMA, GAMEDATA/*BA.ALD, esuka_ba.ald
|
||||
WAIA, GAMEDATA/*WA.WAI, esuka_wa.wai
|
||||
BGIA, GAMEDATA/*BA.BGI, esuka_ba.bgi
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
Init, GAMEDATA/*.ini, system39.ini
|
||||
SaveA, , esuka_sa.asd
|
||||
SaveB, , esuka_sb.asd
|
||||
SaveC, , esuka_sc.asd
|
||||
SaveD, , esuka_sd.asd
|
||||
SaveE, , esuka_se.asd
|
||||
SaveF, , esuka_sf.asd
|
||||
SaveG, , esuka_sg.asd
|
||||
SaveH, , esuka_sh.asd
|
||||
SaveI, , esuka_si.asd
|
||||
SaveJ, , esuka_sj.asd
|
||||
SaveK, , esuka_sk.asd
|
||||
SaveL, , esuka_sl.asd
|
||||
SaveM, , esuka_sm.asd
|
||||
SaveN, , esuka_sn.asd
|
||||
SaveO, , esuka_so.asd
|
||||
SaveP, , esuka_sp.asd
|
||||
SaveQ, , esuka_sq.asd
|
||||
SaveR, , esuka_sr.asd
|
||||
SaveS, , esuka_ss.asd
|
||||
SaveT, , esuka_st.asd
|
||||
SaveU, , esuka_su.asd
|
||||
SaveV, , esuka_sv.asd
|
||||
SaveW, , esuka_sw.asd
|
||||
SaveX, , esuka_sx.asd
|
||||
SaveY, , esuka_sy.asd
|
||||
SaveZ, , esuka_sz.asd
|
||||
@@ -1,8 +0,0 @@
|
||||
# Hushaby Baby Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ALD, hushaby_sa.ald
|
||||
GraphicsA, gamedata/*GA.ALD, hushaby_ga.ald
|
||||
WaveA, gamedata/*WA.ALD, hushaby_wa.ald
|
||||
MidiA, gamedata/*MA.ALD, hushaby_ma.ald
|
||||
SaveA, , hushaby_sa.asd
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# instgame: xsystem3.5 data install script
|
||||
#
|
||||
# Author: Hiroshi Naraki<naraki@debian.or.jp>
|
||||
#
|
||||
# $Id: instgame,v 1.5 1999/05/18 21:52:02 chikama Exp $
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
# default value
|
||||
$cdrom_dir = "/cdrom";
|
||||
$dest_base_dir = "~/game";
|
||||
|
||||
# get program name
|
||||
($program) = ($0 =~ m%([^/]+)$%);
|
||||
|
||||
# get home directory
|
||||
$home = $ENV{'HOME'};
|
||||
|
||||
# show usage and exit
|
||||
sub usage {
|
||||
print "Usage: $program [options] <target>.inf \n";
|
||||
print " -c directory cdrom directory.(default: /cdrom) \n";
|
||||
print " -d directory destination game directory.(default: ~/game/<target>)\n";
|
||||
print " -g file game resource file name.(default: ~/game/<target>.gr)\n";
|
||||
print " -k tag keep the tagged file from copying.\n";
|
||||
}
|
||||
|
||||
;# parsing options
|
||||
while (@ARGV) {
|
||||
$_ = shift;
|
||||
s/^-// || do { $target = $_; last };
|
||||
$_ eq 'c' && @ARGV && do { $cdrom_dir = shift; next }; # install from
|
||||
$_ eq 'd' && @ARGV && do { $dest_base_dir = shift; next }; # install to
|
||||
$_ eq 'g' && @ARGV && do { $grfile = shift; next }; # gr-file to
|
||||
$_ eq 'k' && @ARGV && do { @keep_list = (@keep_list, shift); next }; # keep tag
|
||||
$_ eq 'v' && @ARGV && do { $debug = 1; next }; # debug
|
||||
&usage;
|
||||
exit;
|
||||
}
|
||||
|
||||
# nothing target
|
||||
if (! -f $target ) {
|
||||
&usage;
|
||||
exit;
|
||||
}
|
||||
# decide game resource file name
|
||||
$base = $target;
|
||||
$base =~ s%.*/%%;
|
||||
$base =~ s/\.inf//;
|
||||
$dest_dir = $dest_base_dir."/".$base;
|
||||
$grfile = $dest_base_dir."/".$base.".gr";
|
||||
|
||||
# expand directory name
|
||||
$dest_dir =~ s/~/$home/;
|
||||
$grfile =~ s/~/$home/;
|
||||
|
||||
# print variables if debug
|
||||
if ($debug) {
|
||||
print "cdrom_dir:$cdrom_dir\n";
|
||||
print "dest_dir:$dest_dir\n";
|
||||
print "grfile :$grfile\n";
|
||||
}
|
||||
|
||||
-d $cdrom_dir || die "$!:$cdrom_dir\n";
|
||||
|
||||
&make_dir($dest_dir);
|
||||
|
||||
open( DAT, $target) || die "Can't open $target:$!\n";
|
||||
|
||||
open( FILE, "> $grfile" ) || die "Can't create file $grfile:$!\n";
|
||||
print FILE "# System 3.5 GameResource File: [$target]\n";
|
||||
|
||||
while( $line = <DAT>) {
|
||||
$line =~ s/\#.*|\s//g;
|
||||
$line eq /^$/ && do { next };
|
||||
@data = split(/,/,$line);
|
||||
$tag = $data[0];
|
||||
$from = $data[1];
|
||||
$to = $data[2];
|
||||
|
||||
if( $to eq '' ) {
|
||||
&view_action("Ignore ", $tag, $from, $to);
|
||||
next;
|
||||
}
|
||||
$to = $dest_dir."/".$to;
|
||||
if( $tag eq '' ) {
|
||||
$keep = 0;
|
||||
} else {
|
||||
$keep = grep(/$tag*/, @keep_list );
|
||||
}
|
||||
if( $keep ) {
|
||||
$from = $cdrom_dir."/".$from ;
|
||||
&view_action("Linking", $tag, $from, $to);
|
||||
# symlink($from, $to) if !$debug;
|
||||
system("ln -s $from $to") if !$debug;
|
||||
} elsif( $from eq '' ) {
|
||||
# &view_action("Create ", $tag, $from, $to);
|
||||
# system("touch $to") if !$debug;
|
||||
&view_action("Skip ", $tag, $from, $to);
|
||||
} else {
|
||||
$from = $cdrom_dir."/".$from ;
|
||||
foreach ( <${from}>) {
|
||||
&view_action("Copying", $tag, $from, $to);
|
||||
system("cp $from $to") if !$debug;
|
||||
}
|
||||
}
|
||||
|
||||
# Create game resource file
|
||||
if( $tag ne '' ) {
|
||||
print "writing: $tag $to\n" if $debug;
|
||||
print FILE "$tag $to\n";
|
||||
}
|
||||
}
|
||||
close(FILE);
|
||||
close(DAT);
|
||||
exit;
|
||||
|
||||
sub view_action {
|
||||
local($action, $tag, $from, $to ) = @_;
|
||||
|
||||
print "$action $tag: $from -> $to\n";
|
||||
}
|
||||
|
||||
# mkdir destination directories.
|
||||
sub make_dir {
|
||||
local($target ) = @_;
|
||||
|
||||
@dest = split(/\//,$target);
|
||||
foreach $dir ( @dest ) {
|
||||
$work .= $dir.'/';
|
||||
print "checking $work\n" if $debug;
|
||||
if( !-d $work ) {
|
||||
print "mkdir $work\n" if $debug;
|
||||
mkdir( $work,0777) || die "Can't mkdir $work:$!\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# kakurezuki Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, tsuki_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, tsuki_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, tsuki_wa.ald
|
||||
SaveA, , tsuki_sa.asd
|
||||
SaveB, , tsuki_sb.asd
|
||||
SaveC, , tsuki_sc.asd
|
||||
SaveD, , tsuki_sd.asd
|
||||
SaveE, , tsuki_se.asd
|
||||
SaveF, , tsuki_sf.asd
|
||||
SaveG, , tsuki_sg.asd
|
||||
SaveH, , tsuki_sh.asd
|
||||
SaveI, , tsuki_si.asd
|
||||
SaveJ, , tsuki_sj.asd
|
||||
SaveK, , tsuki_sk.asd
|
||||
@@ -1,17 +0,0 @@
|
||||
# Ikenai Katsumisensei Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, katsumi_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, katsumi_ga.ald
|
||||
GraphicsB, gamedata/*gb.ald, katsumi_gb.ald
|
||||
WaveA, gamedata/*wa.ald, katsumi_wa.ald
|
||||
SaveA, , katsumi_sa.asd
|
||||
SaveB, , katsumi_sb.asd
|
||||
SaveC, , katsumi_sc.asd
|
||||
SaveD, , katsumi_sd.asd
|
||||
SaveE, , katsumi_se.asd
|
||||
SaveF, , katsumi_sf.asd
|
||||
SaveG, , katsumi_sg.asd
|
||||
SaveH, , katsumi_sh.asd
|
||||
SaveI, , katsumi_si.asd
|
||||
SaveJ, , katsumi_sj.asd
|
||||
gamedata/gaiji.dat, gaiji.dat
|
||||
@@ -1,16 +0,0 @@
|
||||
# Kichikuoh Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, kichiku_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, kichiku_ga.ald
|
||||
GraphicsB, gamedata/*gb.ald, kichiku_gb.ald
|
||||
WaveA, gamedata/*wa.ald, kichiku_wa.ald
|
||||
SaveA, , kichiku_sa.asd
|
||||
SaveB, , kichiku_sb.asd
|
||||
SaveC, , kichiku_sc.asd
|
||||
SaveD, , kichiku_sd.asd
|
||||
SaveE, , kichiku_se.asd
|
||||
SaveF, , kichiku_sf.asd
|
||||
SaveG, , kichiku_sg.asd
|
||||
SaveH, , kichiku_sh.asd
|
||||
SaveI, , kichiku_si.asd
|
||||
SaveJ, , kichiku_sj.asd
|
||||
@@ -1,34 +0,0 @@
|
||||
# MAMATOTO Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, mamatoto_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, mamatoto_ga.ald
|
||||
GraphicsB, GAMEDATA/*GB.ALD, mamatoto_gb.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, mamatoto_wa.ald
|
||||
MidiA, GAMEDATA/*MA.ALD, mamatoto_ma.ald
|
||||
DataA, GAMEDATA/*DA.ALD, mamatoto_da.ald
|
||||
SaveA, , mamatoto_sa.asd
|
||||
SaveB, , mamatoto_sb.asd
|
||||
SaveC, , mamatoto_sc.asd
|
||||
SaveD, , mamatoto_sd.asd
|
||||
SaveE, , mamatoto_se.asd
|
||||
SaveF, , mamatoto_sf.asd
|
||||
SaveG, , mamatoto_sg.asd
|
||||
SaveH, , mamatoto_sh.asd
|
||||
SaveI, , mamatoto_si.asd
|
||||
SaveJ, , mamatoto_sj.asd
|
||||
SaveK, , mamatoto_sk.asd
|
||||
SaveL, , mamatoto_sl.asd
|
||||
SaveM, , mamatoto_sm.asd
|
||||
SaveN, , mamatoto_sn.asd
|
||||
SaveO, , mamatoto_so.asd
|
||||
SaveP, , mamatoto_sp.asd
|
||||
SaveQ, , mamatoto_sq.asd
|
||||
SaveR, , mamatoto_sr.asd
|
||||
SaveS, , mamatoto_ss.asd
|
||||
SaveT, , mamatoto_st.asd
|
||||
SaveU, , mamatoto_su.asd
|
||||
SaveV, , mamatoto_sv.asd
|
||||
SaveW, , mamatoto_sw.asd
|
||||
SaveX, , mamatoto_sx.asd
|
||||
SaveY, , mamatoto_sy.asd
|
||||
SaveZ, , mamatoto_sz.asd
|
||||
@@ -1,17 +0,0 @@
|
||||
# MamoriGami Sama Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, mamori_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, mamori_ga.ald
|
||||
GraphicsB, GAMEDATA/*GB.ALD, mamori_gb.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, mamori_wa.ald
|
||||
MidiA, GAMEDATA/*MA.ALD, mamori_ma.ald
|
||||
SaveA, , mamori_sa.asd
|
||||
SaveB, , mamori_sb.asd
|
||||
SaveC, , mamori_sc.asd
|
||||
SaveD, , mamori_sd.asd
|
||||
SaveE, , mamori_se.asd
|
||||
SaveF, , mamori_sf.asd
|
||||
SaveG, , mamori_sg.asd
|
||||
SaveH, , mamori_sh.asd
|
||||
SaveI, , mamori_si.asd
|
||||
SaveJ, , mamori_sj.asd
|
||||
@@ -1,9 +0,0 @@
|
||||
# Ikusa Miko Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, miko_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, miko_ga.ald
|
||||
WaveA, gamedata/*wa.ald, miko_wa.ald
|
||||
SaveA, , miko_sa.asd
|
||||
SaveB, , miko_sb.asd
|
||||
SaveC, , miko_sc.asd
|
||||
SaveJ, , miko_sj.asd
|
||||
@@ -1,7 +0,0 @@
|
||||
# mugen hoyo Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, mugen_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, mugen_ga.ald
|
||||
SaveA, , mugen_sa.asd
|
||||
SaveB, , mugen_sb.asd
|
||||
SaveC, , mugen_sc.asd
|
||||
@@ -1,41 +0,0 @@
|
||||
# Night Demon Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ald, nd_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ald, nd_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ald, nd_wa.ald
|
||||
BGMA, GAMEDATA/*BA.ald, nd_ba.ald
|
||||
WAIA, GAMEDATA/*WA.wai, nd_wa.wai
|
||||
BGIA, GAMEDATA/*BA.bgi, nd_ba.bgi
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
Init, GAMEDATA/*.ini, system39.ini
|
||||
SACT01, GAMEDATA/SACTEFAM.KLD, SACTEFAM.KLD
|
||||
ALK01, GAMEDATA/AliceLogo.alk, AliceLogo.alk
|
||||
ALK02, GAMEDATA/NightDemonDemo1.alk, NightDemonDemo1.alk
|
||||
ALK03, GAMEDATA/NightDemonDemo2.alk, NightDemonDemo2.alk
|
||||
ALK04, GAMEDATA/NightDemonDemo3.alk, NightDemonDemo3.alk
|
||||
SaveA, , nd_sa.asd
|
||||
SaveB, , nd_sb.asd
|
||||
SaveC, , nd_sc.asd
|
||||
SaveD, , nd_sd.asd
|
||||
SaveE, , nd_se.asd
|
||||
SaveF, , nd_sf.asd
|
||||
SaveG, , nd_sg.asd
|
||||
SaveH, , nd_sh.asd
|
||||
SaveI, , nd_si.asd
|
||||
SaveJ, , nd_sj.asd
|
||||
SaveK, , nd_sk.asd
|
||||
SaveL, , nd_sl.asd
|
||||
SaveM, , nd_sm.asd
|
||||
SaveN, , nd_sn.asd
|
||||
SaveO, , nd_so.asd
|
||||
SaveP, , nd_sp.asd
|
||||
SaveQ, , nd_sq.asd
|
||||
SaveR, , nd_sr.asd
|
||||
SaveS, , nd_ss.asd
|
||||
SaveT, , nd_st.asd
|
||||
SaveU, , nd_su.asd
|
||||
SaveV, , nd_sv.asd
|
||||
SaveW, , nd_sw.asd
|
||||
SaveX, , nd_sx.asd
|
||||
SaveY, , nd_sy.asd
|
||||
SaveZ, , nd_sz.asd
|
||||
@@ -1,32 +0,0 @@
|
||||
# Only You リクルス Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, onlyyou_r_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, onlyyou_r_ga.ald
|
||||
WaveA, gamedata/*WA.ald, onlyyou_r_wa.ald
|
||||
Ain, gamedata/*.ain, System39.ain
|
||||
SaveA, , onlyyou_r_sa.asd
|
||||
SaveB, , onlyyou_r_sb.asd
|
||||
SaveC, , onlyyou_r_sc.asd
|
||||
SaveD, , onlyyou_r_sd.asd
|
||||
SaveE, , onlyyou_r_se.asd
|
||||
SaveF, , onlyyou_r_sf.asd
|
||||
SaveG, , onlyyou_r_sg.asd
|
||||
SaveH, , onlyyou_r_sh.asd
|
||||
SaveI, , onlyyou_r_si.asd
|
||||
SaveJ, , onlyyou_r_sj.asd
|
||||
SaveK, , onlyyou_r_sk.asd
|
||||
SaveL, , onlyyou_r_sl.asd
|
||||
SaveM, , onlyyou_r_sm.asd
|
||||
SaveN, , onlyyou_r_sn.asd
|
||||
SaveO, , onlyyou_r_so.asd
|
||||
SaveP, , onlyyou_r_sp.asd
|
||||
SaveQ, , onlyyou_r_sq.asd
|
||||
SaveR, , onlyyou_r_sr.asd
|
||||
SaveS, , onlyyou_r_ss.asd
|
||||
SaveT, , onlyyou_r_st.asd
|
||||
SaveU, , onlyyou_r_su.asd
|
||||
SaveV, , onlyyou_r_sv.asd
|
||||
SaveW, , onlyyou_r_sw.asd
|
||||
SaveX, , onlyyou_r_sx.asd
|
||||
SaveY, , onlyyou_r_sy.asd
|
||||
SaveZ, , onlyyou_r_sz.asd
|
||||
@@ -1,16 +0,0 @@
|
||||
# Oudou Yusha Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, oudou_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, oudou_ga.ald
|
||||
GraphicsB, gamedata/*gb.ald, oudou_gb.ald
|
||||
WaveA, gamedata/*wa.ald, oudou_wa.ald
|
||||
SaveA, , oudou_sa.asd
|
||||
SaveB, , oudou_sb.asd
|
||||
SaveC, , oudou_sc.asd
|
||||
SaveD, , oudou_sd.asd
|
||||
SaveE, , oudou_se.asd
|
||||
SaveF, , oudou_sf.asd
|
||||
SaveG, , oudou_sg.asd
|
||||
SaveH, , oudou_sh.asd
|
||||
SaveI, , oudou_si.asd
|
||||
SaveJ, , oudou_sj.asd
|
||||
@@ -1,36 +0,0 @@
|
||||
# Kaerunyo Panyon Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, panyo_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, panyo_ga.ald
|
||||
WaveA, gamedata/*wa.ald, panyo_wa.ald
|
||||
SaveA, , panyo_sa.asd
|
||||
SaveB, , panyo_sb.asd
|
||||
SaveC, , panyo_sc.asd
|
||||
SaveD, , panyo_sd.asd
|
||||
SaveE, , panyo_se.asd
|
||||
SaveF, , panyo_sf.asd
|
||||
SaveG, , panyo_sg.asd
|
||||
SaveH, , panyo_sh.asd
|
||||
SaveI, , panyo_si.asd
|
||||
SaveJ, , panyo_sj.asd
|
||||
SaveK, , panyo_sk.asd
|
||||
SaveL, , panyo_sl.asd
|
||||
SaveM, , panyo_sm.asd
|
||||
SaveN, , panyo_sn.asd
|
||||
SaveO, , panyo_so.asd
|
||||
, gamedata/floor_1.map, floor_1.map
|
||||
, gamedata/floor_2.map, floor_2.map
|
||||
, gamedata/floor_3.map, floor_3.map
|
||||
, gamedata/floor_4.map, floor_4.map
|
||||
, gamedata/floor_5.map, floor_5.map
|
||||
, gamedata/floor_6.map, floor_6.map
|
||||
, gamedata/floor_7.map, floor_7.map
|
||||
, gamedata/floor_8.map, floor_8.map
|
||||
, gamedata/floor_9.map, floor_9.map
|
||||
, gamedata/floor_10.map, floor_10.map
|
||||
, gamedata/floor_11.map, floor_11.map
|
||||
, gamedata/floor_12.map, floor_12.map
|
||||
, gamedata/floor_13.map, floor_13.map
|
||||
, gamedata/floor_14.map, floor_14.map
|
||||
, gamedata/floor_15.map, floor_15.map
|
||||
, gamedata/floor_16.map, floor_16.map
|
||||
@@ -1,63 +0,0 @@
|
||||
# Kaerunyo Panyon (low price version) Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, panyo_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, panyo_ga.ald
|
||||
WaveA, gamedata/*wa.ald, panyo_wa.ald
|
||||
SaveA, , panyo_sa.asd
|
||||
SaveB, , panyo_sb.asd
|
||||
SaveC, , panyo_sc.asd
|
||||
SaveD, , panyo_sd.asd
|
||||
SaveE, , panyo_se.asd
|
||||
SaveF, , panyo_sf.asd
|
||||
SaveG, , panyo_sg.asd
|
||||
SaveH, , panyo_sh.asd
|
||||
SaveI, , panyo_si.asd
|
||||
SaveJ, , panyo_sj.asd
|
||||
SaveK, , panyo_sk.asd
|
||||
SaveL, , panyo_sl.asd
|
||||
SaveM, , panyo_sm.asd
|
||||
SaveN, , panyo_sn.asd
|
||||
SaveO, , panyo_so.asd
|
||||
SaveP, , panyo_sp.asd
|
||||
SaveQ, , panyo_sq.asd
|
||||
SaveR, , panyo_sr.asd
|
||||
SaveS, , panyo_ss.asd
|
||||
SaveT, , panyo_st.asd
|
||||
SaveU, , panyo_su.asd
|
||||
SaveV, , panyo_sv.asd
|
||||
SaveW, , panyo_sw.asd
|
||||
SaveX, , panyo_sx.asd
|
||||
SaveY, , panyo_sy.asd
|
||||
, gamedata/floor_1.map, floor_1.map
|
||||
, gamedata/floor_2.map, floor_2.map
|
||||
, gamedata/floor_3.map, floor_3.map
|
||||
, gamedata/floor_4.map, floor_4.map
|
||||
, gamedata/floor_5.map, floor_5.map
|
||||
, gamedata/floor_6.map, floor_6.map
|
||||
, gamedata/floor_7.map, floor_7.map
|
||||
, gamedata/floor_8.map, floor_8.map
|
||||
, gamedata/floor_9.map, floor_9.map
|
||||
, gamedata/floor_10.map, floor_10.map
|
||||
, gamedata/floor_11.map, floor_11.map
|
||||
, gamedata/floor_12.map, floor_12.map
|
||||
, gamedata/floor_13.map, floor_13.map
|
||||
, gamedata/floor_14.map, floor_14.map
|
||||
, gamedata/floor_15.map, floor_15.map
|
||||
, gamedata/floor_16.map, floor_16.map
|
||||
, gamedata/floor_17.map, floor_17.map
|
||||
, gamedata/floor_18.map, floor_18.map
|
||||
, gamedata/floor_19.map, floor_19.map
|
||||
, gamedata/floor_20.map, floor_20.map
|
||||
, gamedata/floor_21.map, floor_21.map
|
||||
, gamedata/floor_22.map, floor_22.map
|
||||
, gamedata/floor_23.map, floor_23.map
|
||||
, gamedata/floor_24.map, floor_24.map
|
||||
, gamedata/floor_25.map, floor_25.map
|
||||
, gamedata/floor_26.map, floor_26.map
|
||||
, gamedata/floor_27.map, floor_27.map
|
||||
, gamedata/floor_28.map, floor_28.map
|
||||
, gamedata/floor_29.map, floor_29.map
|
||||
, gamedata/floor_30.map, floor_30.map
|
||||
, gamedata/floor_31.map, floor_31.map
|
||||
, gamedata/floor_32.map, floor_32.map
|
||||
, gamedata/floor_33.map, floor_33.map
|
||||
@@ -1,33 +0,0 @@
|
||||
# PastelChime Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ALD, pascha_sa.ald
|
||||
GraphicsA, gamedata/*GA.ALD, pascha_ga.ald
|
||||
WaveA, gamedata/*WA.ALD, pascha_wa.ald
|
||||
MidiA, gamedata/*MA.ALD, pascha_ma.ald
|
||||
DataA, gamedata/*DA.ALD, pascha_da.ald
|
||||
SaveA, , pascha_sa.asd
|
||||
SaveB, , pascha_sb.asd
|
||||
SaveC, , pascha_sc.asd
|
||||
SaveD, , pascha_sd.asd
|
||||
SaveE, , pascha_se.asd
|
||||
SaveF, , pascha_sf.asd
|
||||
SaveG, , pascha_sg.asd
|
||||
SaveH, , pascha_sh.asd
|
||||
SaveI, , pascha_si.asd
|
||||
SaveJ, , pascha_sj.asd
|
||||
SaveK, , pascha_sk.asd
|
||||
SaveL, , pascha_sl.asd
|
||||
SaveM, , pascha_sm.asd
|
||||
SaveN, , pascha_sn.asd
|
||||
SaveO, , pascha_so.asd
|
||||
SaveP, , pascha_sp.asd
|
||||
SaveQ, , pascha_sq.asd
|
||||
SaveR, , pascha_sr.asd
|
||||
SaveS, , pascha_ss.asd
|
||||
SaveT, , pascha_st.asd
|
||||
SaveU, , pascha_su.asd
|
||||
SaveV, , pascha_sv.asd
|
||||
SaveW, , pascha_sw.asd
|
||||
SaveX, , pascha_sx.asd
|
||||
SaveY, , pascha_sy.asd
|
||||
SaveZ, , pascha_sz.asd
|
||||
@@ -1,20 +0,0 @@
|
||||
# Persiom Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, persiom_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, persiom_ga.ald
|
||||
GraphicsB, GAMEDATA/*GB.ALD, persiom_gb.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, persiom_wa.ald
|
||||
MidiA, GAMEDATA/*MA.ALD, persiom_ma.ald
|
||||
DataA, GAMEDATA/*DA.ALD, persiom_da.ald
|
||||
SaveA, , persiom_sa.asd
|
||||
SaveB, , persiom_sb.asd
|
||||
SaveC, , persiom_sc.asd
|
||||
SaveD, , persiom_sd.asd
|
||||
SaveE, , persiom_se.asd
|
||||
SaveF, , persiom_sf.asd
|
||||
SaveG, , persiom_sg.asd
|
||||
SaveH, , persiom_sh.asd
|
||||
SaveI, , persiom_si.asd
|
||||
SaveJ, , persiom_sj.asd
|
||||
SaveK, , persiom_sk.asd
|
||||
SaveL, , persiom_sl.asd
|
||||
@@ -1,17 +0,0 @@
|
||||
# prostudent good Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, progood_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, progood_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, progood_wa.ald
|
||||
DataA, GAMEDATA/*DA.ALD, progood_da.ald
|
||||
SaveA, , progood_sa.asd
|
||||
SaveB, , progood_sb.asd
|
||||
SaveC, , progood_sc.asd
|
||||
SaveD, , progood_sd.asd
|
||||
SaveE, , progood_se.asd
|
||||
SaveF, , progood_sf.asd
|
||||
SaveG, , progood_sg.asd
|
||||
SaveH, , progood_sh.asd
|
||||
SaveI, , progood_si.asd
|
||||
SaveJ, , progood_sj.asd
|
||||
SaveK, , progood_sk.asd
|
||||
@@ -1,20 +0,0 @@
|
||||
# Rance 1 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, rance1/rancesa.ald, rance1_sa.ald
|
||||
GraphicsA, rance1/rancega.ald, rance1_ga.ald
|
||||
MidiA, rance1/rancema.ald, rance1_ma.ald
|
||||
WaveA, rance1/rancewa.ald, rance1_wa.ald
|
||||
SaveA, , rance1_sa.asd
|
||||
SaveB, , rance1_sb.asd
|
||||
SaveC, , rance1_sc.asd
|
||||
SaveD, , rance1_sd.asd
|
||||
SaveE, , rance1_se.asd
|
||||
SaveF, , rance1_sf.asd
|
||||
SaveG, , rance1_sg.asd
|
||||
SaveH, , rance1_sh.asd
|
||||
SaveI, , rance1_si.asd
|
||||
SaveJ, , rance1_sj.asd
|
||||
SaveK, , rance1_sk.asd
|
||||
SaveL, , rance1_sl.asd
|
||||
SaveM, , rance1_sm.asd
|
||||
SaveN, , rance1_sn.asd
|
||||
@@ -1,20 +0,0 @@
|
||||
# Rance 2 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, rance2/rance2sa.ald, rance2_sa.ald
|
||||
GraphicsA, rance2/rance2ga.ald, rance2_ga.ald
|
||||
MidiA, rance2/rance2ma.ald, rance2_ma.ald
|
||||
WaveA, rance2/rance2wa.ald, rance2_wa.ald
|
||||
SaveA, , rance2_sa.asd
|
||||
SaveB, , rance2_sb.asd
|
||||
SaveC, , rance2_sc.asd
|
||||
SaveD, , rance2_sd.asd
|
||||
SaveE, , rance2_se.asd
|
||||
SaveF, , rance2_sf.asd
|
||||
SaveG, , rance2_sg.asd
|
||||
SaveH, , rance2_sh.asd
|
||||
SaveI, , rance2_si.asd
|
||||
SaveJ, , rance2_sj.asd
|
||||
SaveK, , rance2_sk.asd
|
||||
SaveL, , rance2_sl.asd
|
||||
SaveM, , rance2_sm.asd
|
||||
SaveN, , rance2_sn.asd
|
||||
@@ -1,15 +0,0 @@
|
||||
# Rance 3 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, rance3/rance3sa.ald, rance3sa.ald
|
||||
GraphicsA, rance3/rance3ga.ald, rance3ga.ald
|
||||
WaveA, rance3/rance3wa.ald, rance3wa.ald
|
||||
MidiA, rance3/rance3ma.ald, rance3ma.ald
|
||||
DataA, rance3/rance3da.ald, rance3da.ald
|
||||
SaveA, , rance3sa.asd
|
||||
SaveB, , rance3sb.asd
|
||||
SaveC, , rance3sc.asd
|
||||
SaveD, , rance3sd.asd
|
||||
SaveE, , rance3se.asd
|
||||
SaveF, , rance3sf.asd
|
||||
SaveG, , rance3sg.asd
|
||||
SaveH, , rance3sh.asd
|
||||
@@ -1,15 +0,0 @@
|
||||
# Rance 4 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, rance4_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, rance4_ga.ald
|
||||
WaveA, gamedata/*wa.ald, rance4_wa.ald
|
||||
SaveA, , rance4_sa.asd
|
||||
SaveB, , rance4_sb.asd
|
||||
SaveC, , rance4_sc.asd
|
||||
SaveD, , rance4_sd.asd
|
||||
SaveE, , rance4_se.asd
|
||||
SaveF, , rance4_sf.asd
|
||||
SaveG, , rance4_sg.asd
|
||||
SaveH, , rance4_sh.asd
|
||||
SaveI, , rance4_si.asd
|
||||
SaveJ, , rance4_sj.asd
|
||||
@@ -1,36 +0,0 @@
|
||||
# Rance5D Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, r5d_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, r5d_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, r5d_wa.ald
|
||||
BGMA, GAMEDATA/*BA.ALD, r5d_ba.ald
|
||||
BGIA, GAMEDATA/*BA.BGI, r5d_ba.bgi
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
Init, GAMEDATA/*.ini, system39.ini
|
||||
SACT01, GAMEDATA/SACTEFAM.KLD, SACTEFAM.KLD
|
||||
SaveA, , r5d_sa.asd
|
||||
SaveB, , r5d_sb.asd
|
||||
SaveC, , r5d_sc.asd
|
||||
SaveD, , r5d_sd.asd
|
||||
SaveE, , r5d_se.asd
|
||||
SaveF, , r5d_sf.asd
|
||||
SaveG, , r5d_sg.asd
|
||||
SaveH, , r5d_sh.asd
|
||||
SaveI, , r5d_si.asd
|
||||
SaveJ, , r5d_sj.asd
|
||||
SaveK, , r5d_sk.asd
|
||||
SaveL, , r5d_sl.asd
|
||||
SaveM, , r5d_sm.asd
|
||||
SaveN, , r5d_sn.asd
|
||||
SaveO, , r5d_so.asd
|
||||
SaveP, , r5d_sp.asd
|
||||
SaveQ, , r5d_sq.asd
|
||||
SaveR, , r5d_sr.asd
|
||||
SaveS, , r5d_ss.asd
|
||||
SaveT, , r5d_st.asd
|
||||
SaveU, , r5d_su.asd
|
||||
SaveV, , r5d_sv.asd
|
||||
SaveW, , r5d_sw.asd
|
||||
SaveX, , r5d_sx.asd
|
||||
SaveY, , r5d_sy.asd
|
||||
SaveZ, , r5d_sz.asd
|
||||
@@ -1,31 +0,0 @@
|
||||
# SeeInAO Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, seeinao_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, seeinao_ga.ald
|
||||
WaveA, gamedata/*WA.ald, seeinao_wa.ald
|
||||
MidiA, gamedata/*MA.ald, seeinao_ma.ald
|
||||
SaveA, , seeinao_sa.asd
|
||||
SaveB, , seeinao_sb.asd
|
||||
SaveC, , seeinao_sc.asd
|
||||
SaveD, , seeinao_sd.asd
|
||||
SaveE, , seeinao_se.asd
|
||||
SaveF, , seeinao_sf.asd
|
||||
SaveG, , seeinao_sg.asd
|
||||
SaveH, , seeinao_sh.asd
|
||||
SaveI, , seeinao_si.asd
|
||||
SaveJ, , seeinao_sj.asd
|
||||
SaveK, , seeinao_sk.asd
|
||||
SaveL, , seeinao_sl.asd
|
||||
SaveM, , seeinao_sm.asd
|
||||
SaveN, , seeinao_sn.asd
|
||||
SaveO, , seeinao_so.asd
|
||||
SaveP, , seeinao_sp.asd
|
||||
SaveQ, , seeinao_sq.asd
|
||||
SaveR, , seeinao_sr.asd
|
||||
SaveS, , seeinao_ss.asd
|
||||
SaveT, , seeinao_st.asd
|
||||
SaveU, , seeinao_su.asd
|
||||
SaveV, , seeinao_sv.asd
|
||||
SaveW, , seeinao_sw.asd
|
||||
SaveX, , seeinao_sx.asd
|
||||
SaveY, , seeinao_sy.asd
|
||||
@@ -1,16 +0,0 @@
|
||||
# Toushin Toshi Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, ???_?s?s/*_sa.ald, toushin_sa.ald
|
||||
GraphicsA, ???_?s?s/*_ga.ald, toushin_ga.ald
|
||||
MidiA, ???_?s?s/*_ma.ald, toushin_ma.ald
|
||||
WaveA, ???_?s?s/*_wa.ald, toushin_wa.ald
|
||||
DataA, ???_?s?s/*_da.ald, toushin_da.ald
|
||||
SaveA, , toushin_sa.asd
|
||||
SaveB, , toushin_sb.asd
|
||||
SaveC, , toushin_sc.asd
|
||||
SaveD, , toushin_sd.asd
|
||||
SaveE, , toushin_se.asd
|
||||
SaveF, , toushin_sf.asd
|
||||
SaveG, , toushin_sg.asd
|
||||
SaveH, , toushin_sh.asd
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Toushintoshi2 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, toushin2_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, toushin2_ga.ald
|
||||
WaveA, gamedata/*wa.ald, toushin2_wa.ald
|
||||
DataA, gamedata/*da.ald, toushin2_da.ald
|
||||
SaveA, , toushin2_sa.asd
|
||||
SaveB, , toushin2_sb.asd
|
||||
SaveC, , toushin2_sc.asd
|
||||
SaveD, , toushin2_sd.asd
|
||||
SaveE, , toushin2_se.asd
|
||||
SaveF, , toushin2_sf.asd
|
||||
SaveG, , toushin2_sg.asd
|
||||
SaveH, , toushin2_sh.asd
|
||||
SaveI, , toushin2_si.asd
|
||||
SaveJ, , toushin2_sj.asd
|
||||
@@ -1,32 +0,0 @@
|
||||
# 妻みぐい Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*SA.ald, tsuma_sa.ald
|
||||
GraphicsA, gamedata/*GA.ald, tsuma_ga.ald
|
||||
WaveA, gamedata/*WA.ald, tsuma_wa.ald
|
||||
Ain, gamedata/*.ain, system39.ain
|
||||
SaveA, , tsuma_sa.asd
|
||||
SaveB, , tsuma_sb.asd
|
||||
SaveC, , tsuma_sc.asd
|
||||
SaveD, , tsuma_sd.asd
|
||||
SaveE, , tsuma_se.asd
|
||||
SaveF, , tsuma_sf.asd
|
||||
SaveG, , tsuma_sg.asd
|
||||
SaveH, , tsuma_sh.asd
|
||||
SaveI, , tsuma_si.asd
|
||||
SaveJ, , tsuma_sj.asd
|
||||
SaveK, , tsuma_sk.asd
|
||||
SaveL, , tsuma_sl.asd
|
||||
SaveM, , tsuma_sm.asd
|
||||
SaveN, , tsuma_sn.asd
|
||||
SaveO, , tsuma_so.asd
|
||||
SaveP, , tsuma_sp.asd
|
||||
SaveQ, , tsuma_sq.asd
|
||||
SaveR, , tsuma_sr.asd
|
||||
SaveS, , tsuma_ss.asd
|
||||
SaveT, , tsuma_st.asd
|
||||
SaveU, , tsuma_su.asd
|
||||
SaveV, , tsuma_sv.asd
|
||||
SaveW, , tsuma_sw.asd
|
||||
SaveX, , tsuma_sx.asd
|
||||
SaveY, , tsuma_sy.asd
|
||||
SaveZ, , tsuma_sz.asd
|
||||
@@ -1,38 +0,0 @@
|
||||
# 妻みぐい2 Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, GAMEDATA/*SA.ALD, tsuma2_sa.ald
|
||||
GraphicsA, GAMEDATA/*GA.ALD, tsuma2_ga.ald
|
||||
WaveA, GAMEDATA/*WA.ALD, tsuma2_wa.ald
|
||||
WaveB, GAMEDATA/*WB.ALD, tsuma2_wb.ald
|
||||
BGMA, GAMEDATA/*BA.ALD, tsuma2_ba.ald
|
||||
WAIA, GAMEDATA/*WA.WAI, tsuma2_wa.wai
|
||||
BGIA, GAMEDATA/*BA.BGI, tsuma2_ba.bgi
|
||||
Ain, GAMEDATA/*.ain, system39.ain
|
||||
Init, GAMEDATA/*.ini, system39.ini
|
||||
SACT01, GAMEDATA/SACTEFAM.KLD, SACTEFAM.KLD
|
||||
SaveA, , tsuma2_sa.asd
|
||||
SaveB, , tsuma2_sb.asd
|
||||
SaveC, , tsuma2_sc.asd
|
||||
SaveD, , tsuma2_sd.asd
|
||||
SaveE, , tsuma2_se.asd
|
||||
SaveF, , tsuma2_sf.asd
|
||||
SaveG, , tsuma2_sg.asd
|
||||
SaveH, , tsuma2_sh.asd
|
||||
SaveI, , tsuma2_si.asd
|
||||
SaveJ, , tsuma2_sj.asd
|
||||
SaveK, , tsuma2_sk.asd
|
||||
SaveL, , tsuma2_sl.asd
|
||||
SaveM, , tsuma2_sm.asd
|
||||
SaveN, , tsuma2_sn.asd
|
||||
SaveO, , tsuma2_so.asd
|
||||
SaveP, , tsuma2_sp.asd
|
||||
SaveQ, , tsuma2_sq.asd
|
||||
SaveR, , tsuma2_sr.asd
|
||||
SaveS, , tsuma2_ss.asd
|
||||
SaveT, , tsuma2_st.asd
|
||||
SaveU, , tsuma2_su.asd
|
||||
SaveV, , tsuma2_sv.asd
|
||||
SaveW, , tsuma2_sw.asd
|
||||
SaveX, , tsuma2_sx.asd
|
||||
SaveY, , tsuma2_sy.asd
|
||||
SaveZ, , tsuma2_sz.asd
|
||||
@@ -1,32 +0,0 @@
|
||||
# Zero-shiki (low price version) Install infomation
|
||||
# Tag, #origin, #destination
|
||||
ScenarioA, gamedata/*sa.ald, zero_sa.ald
|
||||
GraphicsA, gamedata/*ga.ald, zero_ga.ald
|
||||
WaveA, gamedata/*wa.ald, zero_wa.ald
|
||||
SaveA, , zero_sa.asd
|
||||
SaveB, , zero_sb.asd
|
||||
SaveC, , zero_sc.asd
|
||||
SaveD, , zero_sd.asd
|
||||
SaveE, , zero_se.asd
|
||||
SaveF, , zero_sf.asd
|
||||
SaveG, , zero_sg.asd
|
||||
SaveH, , zero_sh.asd
|
||||
SaveI, , zero_si.asd
|
||||
SaveJ, , zero_sj.asd
|
||||
SaveK, , zero_sk.asd
|
||||
SaveL, , zero_sl.asd
|
||||
SaveM, , zero_sm.asd
|
||||
SaveN, , zero_sn.asd
|
||||
SaveO, , zero_so.asd
|
||||
SaveP, , zero_sp.asd
|
||||
SaveQ, , zero_sq.asd
|
||||
SaveR, , zero_sr.asd
|
||||
SaveS, , zero_ss.asd
|
||||
SaveT, , zero_st.asd
|
||||
SaveU, , zero_su.asd
|
||||
SaveV, , zero_sv.asd
|
||||
SaveW, , zero_sw.asd
|
||||
SaveX, , zero_sx.asd
|
||||
SaveY, , zero_sy.asd
|
||||
, gamedata/map_data.dat, MAP_DATA.DAT
|
||||
, gamedata/ur_data.dat, UR_DATA.DAT
|
||||
@@ -1,23 +0,0 @@
|
||||
BUG INFORMATION for
|
||||
System 3.5 for X Window System Version 1.7
|
||||
|
||||
|
||||
* いけないかつみ先生の外字はサポートしていません。
|
||||
|
||||
|
||||
* Ctrl+Cで強制終了したり、異常終了した場合 IPC Keyがまれに残る事があります。
|
||||
% ipcs
|
||||
でidを確認し、
|
||||
% ipcrm shm ???
|
||||
で消して下さい。(Linuxの場合。その他は man ipcrm してください)
|
||||
|
||||
|
||||
* SDL を有効にしてコンパイルしフルスクリーンで実行した場合、真中クリックに
|
||||
よるメニューが表示されません。その場合、F4 キーで一旦フルスクリーンを解除
|
||||
してください。
|
||||
|
||||
|
||||
* フルスクリーン実行中にマウスの真中ボタンクリックによるポップアップ
|
||||
メニューを出すと、マウスカーソルが画面の表示領域を超えて動いてしまいます。
|
||||
|
||||
[EOF]
|
||||
@@ -0,0 +1,42 @@
|
||||
set(SOURCES
|
||||
xsystem35.6.adoc
|
||||
xsystem35.gr.5.adoc
|
||||
)
|
||||
|
||||
find_program(ASCIIDOCTOR asciidoctor)
|
||||
if (NOT ASCIIDOCTOR)
|
||||
message(WARNING "asciidoctor not found, manpages will not be generated")
|
||||
else()
|
||||
if (WIN32)
|
||||
set(BACKEND html)
|
||||
else()
|
||||
set(BACKEND manpage)
|
||||
include(GNUInstallDirs)
|
||||
endif()
|
||||
|
||||
set(MANUALS "")
|
||||
foreach(SRC ${SOURCES})
|
||||
string(REGEX MATCH "^(.*)\\.([0-9])\\.adoc$" _ ${SRC})
|
||||
set(NAME ${CMAKE_MATCH_1})
|
||||
set(SECTION ${CMAKE_MATCH_2})
|
||||
if (WIN32)
|
||||
set(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.html)
|
||||
else()
|
||||
set(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.${SECTION})
|
||||
install(FILES ${OUTPUT} DESTINATION ${CMAKE_INSTALL_MANDIR}/man${SECTION})
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SRC}
|
||||
COMMAND ${ASCIIDOCTOR}
|
||||
--attribute xsystem35-version=${XSYSTEM35_VERSION}
|
||||
--backend ${BACKEND}
|
||||
--out-file ${OUTPUT}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/${SRC}
|
||||
)
|
||||
list(APPEND MANUALS ${OUTPUT})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(manual ALL DEPENDS ${MANUALS})
|
||||
endif()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
Q. FreeBSD でコンパイルできない。make とするとエラーが出る。
|
||||
A. gmake (GNU make) を使ってください。
|
||||
|
||||
|
||||
Q. cant load font JISX0201.1976-0 と出て終了してしまう。
|
||||
A. ~/.xsys35rc に自分のシステムにインストールされているフォントを指定して
|
||||
いますか?
|
||||
|
||||
% xlsfonts | grep 0-0-0-0 | grep jisx0201.1976
|
||||
|
||||
とやって表示されるフォント名を ~/.xsys35rc の font_gothic と font_mincho
|
||||
に書きます。今の所この2つしか使用していません。
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
それぞれのゲームに付いての注意書き
|
||||
|
||||
[鬼畜王ランス]
|
||||
|
||||
[ランス4(for Win95)]
|
||||
|
||||
[闘神都市2(for Win95)]
|
||||
|
||||
[いけないかつみ先生]
|
||||
= 外字データには対応していません
|
||||
|
||||
[かえるにょぱにょ〜ん]
|
||||
= フロアデータ(floor_1.map 〜 floor_16.map) はセーブデータと同じディレク
|
||||
トリに置いてください。
|
||||
= 一部のキャラの絵と説明が違う(金魚と????)
|
||||
|
||||
[戦巫女]
|
||||
= 戦巫女は2枚組のCDで、Windows版では一部のデータをCD-ROMに置いたままゲーム
|
||||
することになっていますが、xsystem35 では CD-Audio と CD-ROM上のデータを
|
||||
同時に使用することは出来ません。ハードディスク等に全てのデータをインス
|
||||
トールするか、あるいは CD-Audio 以外の演奏方法(MP3等)を使用してください。
|
||||
CD-Audio を使う場合は2枚目の CD に音楽データが入っています。
|
||||
|
||||
[零式(アリスの館456版)]
|
||||
= マップデータ(MAP_DATA.DAT, UR_DATA.DATA)はセーブデータと同じディレクトリに
|
||||
置いてください。
|
||||
|
||||
[ATLACH=NACHA(アリスの館456版)]
|
||||
|
||||
[人間狩り(アリスの館456版)]
|
||||
|
||||
[アリスの館456]
|
||||
- DALK
|
||||
|
||||
- 闘神都市
|
||||
|
||||
- DrStop!
|
||||
|
||||
- ランス1
|
||||
|
||||
- ランス2
|
||||
|
||||
- ランス3
|
||||
|
||||
[王道勇者]
|
||||
|
||||
[ディアボリカ]
|
||||
|
||||
[AmbivalenZ(for Win95)]
|
||||
|
||||
[夢幻泡影 (for Win95)]
|
||||
|
||||
[ぱすてるチャイム]
|
||||
= ぱすてるチャイムでは明らかにシナリオにおかしいところがあります。
|
||||
そこで、これに対するパッチを作成しました。
|
||||
|
||||
patch というディレクトリの下に pastel.diff というパッチ当てのスクリプト
|
||||
がありますので、ゲームのシナリオデータのあるディレクトリにコピーして
|
||||
そのディレクトリに移動した後、 ./pastel.diff (シナリオファイル名) と
|
||||
実行してください。
|
||||
(例)
|
||||
> ./pastel.diff pastel_sa.ald
|
||||
|
||||
古いデータは pastel_sa.ald.org として保存されます。
|
||||
シナリオデータは Ver 1.00 と Ver 1.01 に適用できます。
|
||||
|
||||
(廉価版では直っている?)
|
||||
|
||||
[ぷろすちゅーでんとGood]
|
||||
|
||||
[守り神様]
|
||||
|
||||
[ママトト]
|
||||
|
||||
[HushByBaby]
|
||||
|
||||
[Darcrows]
|
||||
|
||||
[PERSIOM]
|
||||
= DirectSound(3D Sound) には対応していません。
|
||||
|
||||
[隠れ月]
|
||||
|
||||
[SeeIn青]
|
||||
|
||||
[廉価版・デアボリカ]
|
||||
|
||||
[廉価版・アトラクナクア]
|
||||
|
||||
[廉価版・かえるにょ・ぱにょん]
|
||||
|
||||
[廉価版・零式]
|
||||
|
||||
[かえるにょ国にょありす(20世紀アリス)]
|
||||
= 外部MIDIプレイヤーモードでは音楽がリピートされません。
|
||||
|
||||
[これDPS?(20世紀アリス)]
|
||||
|
||||
[夜が来る!]
|
||||
= 動きません
|
||||
|
||||
[王子さま Lv1]
|
||||
= 動きません
|
||||
|
||||
[Only You 〜リ・クルス〜]
|
||||
|
||||
[大悪司]
|
||||
= オープニングが動かない
|
||||
|
||||
[王子さま Lv1.5]
|
||||
|
||||
[妻みぐい]
|
||||
= オープニングが動かない
|
||||
|
||||
[エスカレイヤー]
|
||||
= オープニングが動かない
|
||||
|
||||
[Rance5D]
|
||||
|
||||
[妻みぐい2]
|
||||
= オープニングが動かない
|
||||
|
||||
[DALK外伝]
|
||||
[ままにょにょ]
|
||||
= System4につき動きません
|
||||
|
||||
|
||||
/*
|
||||
* その他
|
||||
*/
|
||||
|
||||
* センチメンタルシーズン (Aliceの館 CDをベースに UNITBASE が移植したもの)
|
||||
= http://www4.big.or.jp/~unitbase/sys35sdk/games.html
|
||||
|
||||
* Intruder (Aliceの館 CDをベースに UNITBASE が移植したもの)
|
||||
= まだ完了していません
|
||||
|
||||
* Crecent Moon がぁる (Aliceの館 CDをベースに UNITBASE が移植したもの)
|
||||
= http://www4.big.or.jp/~unitbase/sys35sdk/games.html
|
||||
|
||||
* あぶない天狗伝説 (Aliceの館 CD2をベースに UNITBASE が移植したもの)
|
||||
= 掲載許可が下りません
|
||||
|
||||
* OnlyYou (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* ランス4.1 (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
= スプライトの色がおかしい
|
||||
|
||||
* ランス4.2 (system3版をベースに TOTOさんが移植したもの
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
= スプライトの色がおかしい
|
||||
|
||||
* アリスの館3 (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* 乙女戦記 (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* 闘神都市2外伝 (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* DPS全部 (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* ProStudentG (system3版をベースに TOTOさんが移植したもの)
|
||||
= http://www1.interq.or.jp/t-takeda/index.html
|
||||
|
||||
* 学園KING (AliceCD Ver 1.??)
|
||||
= CDが鳴らない
|
||||
-> TOTOさんが、CD-DAに対応するためのシナリオパッチをつくっています。
|
||||
http://www1.interq.or.jp/t-takeda/midi.html
|
||||
|
||||
|
||||
* メイドのススメ (AliceCD Ver 1.02)
|
||||
|
||||
* ぶろぶろ (AliceCD Ver 1.02)
|
||||
|
||||
* 学園漂流戦記 (AliceCD Ver 1.02)
|
||||
|
||||
* 扉 (AliceCD Ver 2.02)
|
||||
|
||||
* 妖精 (AliceCD Ver 2.02)
|
||||
|
||||
/*
|
||||
* 3rd party 製作 ゲーム
|
||||
*/
|
||||
|
||||
#include "MISCGAME.TXT"
|
||||
@@ -1,80 +0,0 @@
|
||||
ゲームリソースファイル(*.gr)の書き方
|
||||
|
||||
|
||||
xsystem35ではゲームデータの置き場所をテキストファイルに保存しておき、
|
||||
それをxsystem35に指定してやる事でデータの置き場所を知らせます。
|
||||
|
||||
# このファイルのファイル名はxsystem35を起動する時に指定できますので、hoge.gr
|
||||
# である必要は無く、自由につける事が出来ます。
|
||||
|
||||
ここでは、このテキストファイル(ゲームリソースファイル)の書式を説明します。
|
||||
|
||||
|
||||
1つのデータにつき1行で記述します。例を見て下さい。
|
||||
|
||||
ScenarioA /home/chikama/game/rance4/rance4_sa.ald
|
||||
|
||||
キーワード データのフルパス
|
||||
|
||||
の順に書きます。キーワードは行の先頭から書き、データのフルパスとの間は半角
|
||||
スペースです。(セーブデータのみ、ホームディレクトリを表す ~ を評価します。)
|
||||
|
||||
キーワードには次の7つがあります。
|
||||
|
||||
Scenario : シナリオ
|
||||
Graphics : グラフィック
|
||||
Wave : PCMデータ
|
||||
Midi : MIDIデータ
|
||||
Data : その他データ
|
||||
Save : セーブデータ
|
||||
Resource : リソースファイル(マウスカーソル形状)
|
||||
Ain : DLL とその関数の情報のかかれた System39.ain
|
||||
BGM : BGMデータ(エスカレイヤー以降のBGMデータファイル)
|
||||
WAI : PCMデータの追加情報 (mixer channel 情報等)
|
||||
BGI : BGMデータの追加情報 (ループ位置等)
|
||||
SACT01 : SACT.DLL用データ (SACTEFAM.KLD)
|
||||
ALK : デモ用アーカイブファイル
|
||||
Init : System39.ini
|
||||
|
||||
同じ種類のデータが複数ある場合はキーワードの次にA,B,C...をつけます。
|
||||
(Ain を除く)
|
||||
|
||||
CD-ROM上のどれがどの種類のファイルかは、ファイル名で判別します。
|
||||
*SA.ALD ならシナリオの1番目、*GA.ALDはグラフィック、*WA.ALDはPCM、
|
||||
*MA.ALD はMIDI、*DA.ALDはその他データです。
|
||||
|
||||
Ain は System39.ain へのパスを書きます。例えば
|
||||
Ain /home/chikama/game/20cKaeru/System39.ain
|
||||
のように書きます。
|
||||
|
||||
Init は System39.ini へのパスを書きます。例えば
|
||||
Init /home/chikama/game/esuka/System39.ini
|
||||
のように書きます。
|
||||
|
||||
WAI/BGI は通常1ファイルだけですので
|
||||
WAIA /home/chikama/game/esuka/DOKI_WA.WAI
|
||||
BGIA /home/chikama/game/esuka/DOKI_BA.BGI
|
||||
のように書きます。
|
||||
|
||||
SACT01 は SACTシステム固有のデータファイル(SACTEFAM.KLD)へのパスを
|
||||
指定します。
|
||||
SACT01 /home/chikama/game/rance5d/SACTEFAM.KLD
|
||||
|
||||
ALK はデモ用のデータが入ったアーカイブファイルです。
|
||||
ALK01 /home/chikama/game/nd/NightDemonDemo1.alk
|
||||
ALK02 /home/chikama/game/nd/NightDemonDemo2.alk
|
||||
ALK03 /home/chikama/game/nd/NightDemonDemo3.alk
|
||||
のように書きます。
|
||||
|
||||
|
||||
また、これらの範疇にはいらないデータ、たとえば、かえるにょぱにょんの
|
||||
floor_1.mapなどはリソースファイルを介しませんので、ゲームリソース
|
||||
ファイルには書かず、「セーブデータを置いたディレクトリと同じ場所」に
|
||||
コピーして下さい。
|
||||
|
||||
また、Save? のみ、'~' (ホームディレクトリ)を評価しますので、
|
||||
SaveA ~/savedata/kichiku/kichiku_sa.ald
|
||||
のように書く事が出来ます。
|
||||
|
||||
コメントを書く場合は行頭に # をつけます。また空白行は無視されます。
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
その他の SYSTEM 3.5 のゲームについて
|
||||
|
||||
README にある動作確認ゲーム以外にも、ネット上で公開されているSYSTEM3.5対応
|
||||
のゲームのうち幾つかは動く事が確認されています。ここでは 1.4.0 preX での動作
|
||||
状況です。1.4.0 公開時点で手に入るゲームを順次追加していっています。
|
||||
また、XXXが動かないんだけど調べてください、XXXはちゃんと動きました、といった
|
||||
情報もおよせください。
|
||||
|
||||
OKは問題なく動く。GOODが多少の問題あるもののゲームをやるぶんで気にはなるけど
|
||||
耐えられる範囲のもの。BADは動かないものです。
|
||||
|
||||
|
||||
(作者の敬称略)
|
||||
【 ソフト名 】 【 ファイル名 】【 作者名 】【 動作 】
|
||||
国立ファーブル図
|
||||
書館司書〜
|
||||
ブックルちゃん〜 SSYO_100.LZH 大塚ヒロ OK
|
||||
バルーンぽっぷ BPOP_093.LZH 大塚ヒロ OK
|
||||
|
||||
Self self.lzh よこやまなおき OK
|
||||
おせろっと othellotto.exe よこやまなおき OK
|
||||
キャロル carol.lzh よこやまなおき GOOD(1)
|
||||
|
||||
積層四目並べ Te1.lzh 深叢 忍 OK
|
||||
16Piece Puzzle 16p1.lzh 深叢 忍 OK
|
||||
The Line Line0.lzh 深叢 忍 OK
|
||||
|
||||
Black Jack bj120.lzh かずき OK
|
||||
|
||||
マスカレイド (in AliceCD202) ちあぼう OK
|
||||
戦娘ver2.12 ikusa212.lzh ちあぼう OK
|
||||
|
||||
できるかなー! (in AliceCD202) KIMM SOFT OK(2)
|
||||
|
||||
恋は計算どおり♪ koi.lzh SS OK
|
||||
|
||||
動体視力を鍛えるにょ game_191.lzh LazyRichField BAD(3)
|
||||
集中力を鍛えるにょ game_22.lh LazyRichField GOOD(4)
|
||||
|
||||
|
||||
1) 雪が降らない (grDrawFillCircle が未実装)
|
||||
通信対戦が出来ない
|
||||
2) メッセージサイズ300ドットの表示は 100 ドットに制限しています
|
||||
3) DS 数字 となっている
|
||||
4) 色がおかしい
|
||||
|
||||
最後にこれらのデータを公開している作者の方々のURLです。
|
||||
|
||||
大塚ヒロさん http://www.studioHIRO.com/
|
||||
よこやまなおきさん http://www3.justnet.ne.jp/~naoki-yokoyama/
|
||||
深叢 忍さん http://www2s.biglobe.ne.jp/~nin/
|
||||
かずきさん http://www.din.or.jp/~kazuki/
|
||||
桐ヶ屋 inc.(ちあぼう)さん http://www.eucaly.net/~kiri
|
||||
KIMM SOFT/ネリワサヴィちーむ heavyd@syd.odn.ne.jp
|
||||
SSさん http://www.sspzgr.net/
|
||||
LazyRichFieldさん http://www2j.biglobe.ne.jp/~Lazy/staff/alice.htm
|
||||
|
||||
-314
@@ -1,314 +0,0 @@
|
||||
|
||||
System 3.5 for X Window System Version 1.7 (開発版)
|
||||
|
||||
0. 目次
|
||||
|
||||
1. はじめに
|
||||
2. 開発環境と動作確認環境
|
||||
2.a. 開発環境
|
||||
2.b. 動作確認環境
|
||||
2.c. 多分動くと思われる環境
|
||||
3. xystem35の設定ファイル
|
||||
4. ゲームの実行と終了
|
||||
4.a. ゲームの実行
|
||||
4.b. ゲームの終了
|
||||
4.c. xsystem35メニュー
|
||||
4.d. キーボード操作
|
||||
5. コマンドラインオプション
|
||||
6. その他情報
|
||||
6.a. SYSV IPC
|
||||
6.b. SharedMemoryの大きさについて
|
||||
6.c. locale と X
|
||||
7. 著作権とライセンス
|
||||
8. サポート
|
||||
|
||||
|
||||
1. はじめに
|
||||
|
||||
このソフトは AliceSoft の Syetem3.5/3.6/3.8/3.9 上で動くゲームを
|
||||
X Window System 上で動かすためのプログラムです。
|
||||
|
||||
* 動作確認ゲーム
|
||||
|
||||
鬼畜王ランス
|
||||
ランス4 (for Win95)
|
||||
闘神都市II (for Win95)
|
||||
夢幻泡影 (for Win95)
|
||||
かえるにょぱにょ〜ん (初版、廉価版)
|
||||
いけないかつみ先生
|
||||
戦巫女 (初版、廉価版)
|
||||
零式 (館456版、廉価版)
|
||||
ATLACH=NACHA (館456版、廉価版)
|
||||
人間狩り
|
||||
王道勇者
|
||||
ディアボリカ (初版、廉価版)
|
||||
AmbivalenZ(for Win95)
|
||||
ぱすてるチャイム
|
||||
ぷろすちゅーでんとGood
|
||||
守り神様
|
||||
ママトト
|
||||
|
||||
アリスの館456
|
||||
DALK
|
||||
闘神都市
|
||||
DrStop!
|
||||
ランス1
|
||||
ランス2
|
||||
ランス3
|
||||
|
||||
アリスCD Ver 1.02
|
||||
メイドのすすめ
|
||||
ぶろぶろ
|
||||
学園漂流戦記
|
||||
|
||||
Hushaby Baby
|
||||
Darcrows
|
||||
PERSIOM
|
||||
隠れ月
|
||||
SeeIn青
|
||||
|
||||
20世紀アリス
|
||||
これDPS?
|
||||
かえるにょ国にょアリス
|
||||
|
||||
アリスCD Ver 2.02
|
||||
扉
|
||||
妖精
|
||||
|
||||
OnlyYou リ・クルス
|
||||
王子さま Lv1.5
|
||||
|
||||
* 動かないゲーム
|
||||
|
||||
夜が来る!
|
||||
王子さま Lv1
|
||||
DALK外伝 (System4)
|
||||
ままにょにょ (System4)
|
||||
|
||||
* 一部動作可能
|
||||
|
||||
大悪司 (オープニング不可)
|
||||
妻みぐい (オープニング不可)
|
||||
エスカレイヤー (オープニング不可)
|
||||
Rance5D
|
||||
妻みぐい2 (オープニング不可)
|
||||
シェル・クレイル
|
||||
NightDemon (オープニング不可)
|
||||
|
||||
|
||||
各ゲームについての補足事項は doc/GAMES.TXT を御覧ください。
|
||||
|
||||
また、ネット上で公開されているSYSTEM3.5用のゲームのうち、いくつかは
|
||||
動作が確認されています。くわしくは MISCGAME.TXT を御覧下さい。
|
||||
|
||||
|
||||
2. 開発環境と動作確認環境
|
||||
|
||||
2.a. 開発環境
|
||||
|
||||
Kondara-1.2 (Linux/i386)
|
||||
|
||||
2.b. 動作確認環境
|
||||
|
||||
DigitalUnix 4.0F (alpha)
|
||||
SunOS 5.8 (sparc)
|
||||
|
||||
2.c. 多分動くと思われる環境
|
||||
|
||||
Linux(!i386)
|
||||
FreeBSD/NetBSD/OpenBSD (i386 etc)
|
||||
Irix 6.X
|
||||
Solaris 2.X 以降 (sparc/i386)
|
||||
|
||||
|
||||
3. xystem35の設定ファイル
|
||||
|
||||
xsystem35ではフォントの設定などを、カレントディレクトリとホームディレクトリに
|
||||
ある .xsys35rcというファイルから読み込みます。カレントディレクトリの .xsys35rc
|
||||
に書かれた設定のほうが優先されるため、一般的な設定はホームディレクトリの
|
||||
.xsys35rcに書き、特定のゲームで書き換えたい項目はゲームを置いたディレクトリの
|
||||
.xsys35rcに書くといいでしょう。
|
||||
|
||||
サンプルとなるファイルを xsys35rc.sample として用意しました。
|
||||
|
||||
また、gtk widgetのリソースは ~/.gtk/gtkrc または ~/.gtkrc から読み込みます。
|
||||
読み込み先は .xsys35rc で指定できます。
|
||||
|
||||
ゲームで使用するフォントは X のフォントの仕組を使う方法と、TrueTypeFontを
|
||||
外部ライブラリを使って直接表示する方法の2種類が選べます。
|
||||
|
||||
~/.xsys35rc の ttfont_mincho: と ttfont_gothic: の場所にフォントファイルを
|
||||
指定し、font_device: ttf と指定してください。詳しくはサンプルファイル
|
||||
(xsys35rc.sample)を見て下さい。
|
||||
|
||||
|
||||
4. ゲームの実行と終了
|
||||
|
||||
4.a. ゲームの実行
|
||||
|
||||
instgameを使ってゲームデータをインストールすると、インストールした
|
||||
ディレクトリにの一つ上に ???????.gr というファイル(ゲームリソースファイル)
|
||||
が自動的に作成されますので
|
||||
|
||||
% xsystem35 -gamefile ~/game/???????.gr
|
||||
|
||||
のように実行します。-gamefile オプションをつけない場合は、カレントディレク
|
||||
トリの xsystem35.gr を参照します。
|
||||
|
||||
またいずれも見つからない場合は、カレントディレクトリにあるファイルをゲーム
|
||||
データとして設定します。例えば testSA.ALD というファイルがカレントディレク
|
||||
トリにあると、それをシナリオデータとして使います。またセーブデータは適当な
|
||||
名前で 10個確保されます。
|
||||
|
||||
ゲームリソースファイルの書き方については、doc/GRFMT.TXT を御覧下さい。
|
||||
|
||||
4.b. ゲームの終了
|
||||
|
||||
* 起動した端末で Ctrl+C を押すと、すぐに終了します。
|
||||
|
||||
* ゲーム中にマウスの真中のボタンを押すと、メニューが出て来ますので、
|
||||
そこから 'Exit' を選んで終了させて下さい。Window Manager のCloseボタンでも
|
||||
同じように動作します。
|
||||
|
||||
* ESC + SPACE + RET キーを同時に押すと、すぐに終了します。
|
||||
|
||||
4.c. xsystem35メニュー
|
||||
|
||||
ゲーム中にマウスの真中ボタンを押すとメニューが出て来ます。
|
||||
|
||||
メッセージスキップ(MessageSkip):
|
||||
次の選択肢までメッセージを飛ばします。マウスの右クリックで
|
||||
止まります。シナリオによっては停止しない場合もあります。
|
||||
|
||||
マウスカーソル移動(MouseAutoMove):
|
||||
選択ウィンドなどでマウスが自動的に移動するのを制御します。
|
||||
|
||||
ボリュームバランス(VolumeValance):
|
||||
SACTシステムで音声の人物毎に音量を変えることができます。
|
||||
(ゲームがそう出来るように対応している場合のみ)
|
||||
|
||||
情報(About):
|
||||
このゲームについて。
|
||||
|
||||
終了(Exit):
|
||||
ゲームを終了します。
|
||||
|
||||
4.d. キーボード操作
|
||||
|
||||
アリスソフトのゲームは有史来、基本的に上下左右キーに SPACE, RET, ESC, TAB
|
||||
キーしか使いません(一部のゲームはそれ以外のキーを使うこともあります)。
|
||||
XSystem35 では次のキー割当を特別に使っています。
|
||||
|
||||
F1: メッセージスキップ
|
||||
F4: フルスクリーン <-> Window表示の切替え
|
||||
|
||||
また、旧き良き時代の DOS への復帰キー (ESC + SPACE + RET) でゲームを確認なし
|
||||
で終了出来ます。
|
||||
|
||||
|
||||
5. コマンドラインオプション
|
||||
|
||||
-h : ヘルプを表示します。
|
||||
|
||||
-gamefile file : ゲームリソースファイルを file から読み込みます。
|
||||
|
||||
-no-shm : ネットワーク越しに起動する場合に指定します。
|
||||
|
||||
-devcd name : CD-ROM device名を name にします。
|
||||
-devmidi name : MIDI device名を name にします。
|
||||
|
||||
-M? : MIDI の出力の種類を選択します。
|
||||
-Me : External midi player (default)
|
||||
-Mr : Raw Midi device
|
||||
-Ms? : Sequencer device (?:device number)
|
||||
-M0 : disable midi
|
||||
|
||||
-devjoy n : n 番目の joystick のデバイスを使用します。
|
||||
|
||||
-version : バージョン情報を表示します。
|
||||
|
||||
-fullscreen : フルスクリーンでゲームを実行します。
|
||||
SDL 有効時には Widget にフォーカスが当たらないため Popup
|
||||
menu 等が使用できません。
|
||||
|
||||
-antialias : 常に文字にアンチエリアスをかけます。
|
||||
|
||||
-noantialias : 常に文字にアンチエリアスをかけません。シナリオ上でアンチ
|
||||
エリアスの指定があっても無視します。
|
||||
|
||||
-devfont : フォントデバイスを指定します。x11/ft2/sdl のいずれかが
|
||||
使えます。
|
||||
|
||||
-noimagecursor : リソースファイルによるカーソル形状の変更(Darcrows等)を無効
|
||||
にしたい場合に指定します。
|
||||
|
||||
|
||||
6. その他情報
|
||||
|
||||
6.a. SYSV IPC
|
||||
|
||||
カーネルに SYSV IPC (SharedMemory/MessageQueue/Semaphore)の機能が
|
||||
組み込まれている必要があります。
|
||||
|
||||
6.b. SharedMemoryの大きさについて
|
||||
|
||||
カーネルのSharedMemoryの大きさが十分大きい場合、MIT-SHMを利用して画面を
|
||||
高速に描画する事が出来ます。起動時にエラーが出る場合、カーネルパラメータを
|
||||
変更してみて下さい。(BSD 系はデフォルトの SharedMemory の大きさが小さい
|
||||
ようです)
|
||||
|
||||
6.b.1. FreeBSDの場合 /usr/src/sys/i386/conf/LINT 中の
|
||||
|
||||
options SHMALL=1025
|
||||
options "SHMMAX=(SHMMAXPGS*PAGE_SIZE+1)"
|
||||
options SHMMAXPGS=1025
|
||||
options SHMMIN=2
|
||||
options SHMMNI=33
|
||||
options SHMSEG=9
|
||||
「SHMMAXPGS」が関連項目です。
|
||||
|
||||
24bppで起動する場合は SHMMAXPGS=4096 くらいにするとよいようです。
|
||||
もちろん設定後はカーネルの再構築が必要です。
|
||||
|
||||
6.c. locale と X
|
||||
|
||||
libc に locale 機構が組み込まれていない環境の場合、libxpg4 などの
|
||||
ライブラリ(FreeBSD) や X の locale を使うことになります。
|
||||
|
||||
linux の libc5 な環境では configure 時に --with-locale=ja_JP.ujis として
|
||||
コンパイルしてください。
|
||||
|
||||
それ以外のほとんどの環境では考慮する必要はないでしょう。
|
||||
|
||||
|
||||
7. 著作権とライセンス
|
||||
|
||||
プログラムの著作権は作者(chikama)に属しますが、ソースコードはGPLに基づいて
|
||||
再配付できます。
|
||||
(一部のソースコードは私以外に著作権があります。詳しくは、ソースコードを御覧
|
||||
下さい)
|
||||
|
||||
ゲーム内で使われてるカーソルフォントの一部をqvwm(Windows95そっくりのWindow
|
||||
Manager)からいただきました。
|
||||
|
||||
|
||||
8. サポート
|
||||
|
||||
動作報告・バグ報告などは以下のメールアドレスまでお願いします。
|
||||
|
||||
E-Mail: chikama@nabal.aist-nara.ac.jp (private)
|
||||
masaki-c@is.aist-nara.ac.jp (official)
|
||||
|
||||
また最新版は以下のURLからダウンロードできます。
|
||||
|
||||
http://www.aist-nara.ac.jp/~masaki-c/private/unitbase/xsys35/index.html
|
||||
|
||||
バグレポートの際は、お使いの環境を出来るだけくわしく書いて下さい。
|
||||
また特定のゲームで動かない場合は、出来るだけセーブデータも添付して下さい。
|
||||
|
||||
プログラムやシナリオデータのバグで 'sigsegv caught @xxx,xxxxx' といって
|
||||
終了する場合は、そのメッセージの部分(xxx,xxxx)も送って下さい。
|
||||
|
||||
可能ならば、./configure で --enable-debug を付けてコンパイルしておき、
|
||||
デバッガ上で実行して segmentation fault で止まった時に、bt と打って出てくる
|
||||
メッセージを添付していただけるととても助かります。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user