Initial commit

This commit is contained in:
987123879113 2021-04-12 10:38:47 +09:00
commit a95d053a37
34 changed files with 6335 additions and 0 deletions

147
.gitignore vendored Normal file
View File

@ -0,0 +1,147 @@
ddr5thmix-solo-src/build
ddr5thmix-solo-src/build_soloio
ddr5thmix-solo-src/tools/build/*
ddr5thmix-solo-src/tools/py/*.c*
ddr5thmix-solo-src/tools/py/*.html
ddr5thmix-solo-src/data_raw
!ddr5thmix-solo-src/data_raw/deleteme.txt
ddr5thmix-solo-src/data_source
!ddr5thmix-solo-src/data_source/deleteme.txt
ddr5thmix-solo-src/data_modified_raw
ddr5thmix-solo-src/\#*
ddr5thmix-solo-src/stepx_*.sh
*.iso
*.zip
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# Dance Dance Revolution 5th Mix Solo
This is an UNOFFICIAL patch for the System 573/arcade version of Dance Dance Revolution 5th Mix.
The game contains left over code that supports 6 panel gameplay and a large number of songs have 6 panel charts left over from 4th Mix Plus. This project is an attempt to restore that functionality as well as make improvements to the UI to bring it closer to what a real 5th Mix Solo might have felt like.
All source code is included.
## KNOWN BUGS/ISSUES
- Lights are not mapped correctly on solo cabinet (probably won't fix but I will accept a working PR)
- Edits are bugged in 6 panel mode (won't fix)
## Instructions
Note: **YOU MUST PROVIDE YOUR OWN DATA!**
[Microsoft Visual Studio 2015 x86 Redistributable](https://www.microsoft.com/en-US/download/details.aspx?id=48145) is required for tools to work properly.
1. Extract the contents of your Dance Dance Revolution 5th Mix CD to the `data_source` folder. You should have a `DAT` folder, `GAME.DAT`, `CARD.DAT`, and `PSX.EXE` file in this folder.
2. Run `step1_extract.bat` (`step1_extract.sh` if you are on *nix) to generate the `data_raw` folder.
3. Run `step2_make.bat` or `step2_make_soloio.bat` (`step2_make.sh` or `step2_make_soloio.sh` if you are on *nix) to build the `ddr5thsolo.iso`/`ddr5thsolo_soloio.iso` files. If you are using a real Solo machine then use `step2_make_soloio.bat`. If you are using MAME or an otherwise non-Solo machine then use `step2_make.bat`.
Additionally, if you are building this for MAME, you must use `chdman` (included with MAME) to build the required CHD.
1. Generate CHD using `chdman.exe createcd -i ddr5thsolo.iso -o a27jaa02.chd -c none` (`-c none` is requested because the compression can throw timing off in MAME).
2. Overwrite `roms/ddr5m/a27jaa02.chd` in your MAME folder with the newly generated `a27jaa02.chd`.
3. Delete `nvram/ddr5m` in order to force reinstallation when you boot `ddr5m` next in MAME.
4. You must run MAME from the command line instead of through the normal MAME UI to bypass hash check errors
If you wish to extract the CD bin/cue from a CHD file, use `chdman.exe extractcd -i a27jaa02.chd -o a27jaa02.cue`.
## Additional Instructions (*NIX ONLY)
If you are building using *nix, you must compile the required Cython modules:
1. `cd tools/py`
2. `python3 -m pip install -r requirements.txt`
2. `python3 setup.py build_ext --inplace`
## Notes
- Dipswitch 1 can be toggled on/off to enable/disable autoplay (even on real hardware)
- There are various flags in [src/main.asm](https://github.com/987123879113/sys573mods/blob/main/ddr5thmix-solo-src/src/main.asm) that can be modified such as `FORCE_UNLOCK`, `SOLO_MODE`, `AUTOPLAY_ENABLED`, `AUTOPLAY_TIMING`, `DISABLE_ANNOUNCER`, and `DISABLE_CHEERING`.
## Thanks
- @WannyTiggah for the edited title screen and icon edits
- @SakamotoNeko13 for the edited 4PANEL/6PANEL graphics on the style select screen
- @dragonminded for testing with a real Solo cabinet

BIN
data_modified/cslbk_16.diff Normal file

Binary file not shown.

BIN
data_modified/mdb.diff Normal file

Binary file not shown.

View File

@ -0,0 +1,58 @@
[
{
"flag_loc": 1,
"flag_comp": 1,
"flag_enc": 1,
"filename": "data/mdb/mdb.bin",
"patch": "mdb.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/gpct/select/sslob_25.cmt",
"patch": "sslob_25.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/gpct/select/sslop_25.cmt",
"patch": "sslop_25.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/lang/japa/sstxt_25.cmt",
"patch": "sstxt_25.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/gpct/select/cslbk_16.cmt",
"patch": "cslbk_16.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/lang/japa/title_25.cmt",
"patch": "title_25.diff",
"patch_format": "bsdiff4"
},
{
"flag_loc": 0,
"flag_comp": 1,
"flag_enc": 0,
"filename": "data/gpct/select/selic_25.cmt",
"patch": "selic_25.diff",
"patch_format": "bsdiff4"
}
]

BIN
data_modified/selic_25.diff Normal file

Binary file not shown.

BIN
data_modified/sslob_25.diff Normal file

Binary file not shown.

BIN
data_modified/sslop_25.diff Normal file

Binary file not shown.

BIN
data_modified/sstxt_25.diff Normal file

Binary file not shown.

BIN
data_modified/title_25.diff Normal file

Binary file not shown.

0
data_source/deleteme.txt Normal file
View File

34
src/autoplay.asm Normal file
View File

@ -0,0 +1,34 @@
.org 0x80012860
CheckAutoplayDipswitch:
lb v0, 0x1f400004
nop
andi v0, 1
beq v0, 0, _CheckAutoplayDipswitchEnd
nop
lb v0, 0x20(s3)
nop
_CheckAutoplayDipswitchEnd:
j CheckAutoplayDipswitchEnd
nop
.org 0x8007f7f8
; Enable autoplay
j CheckAutoplayDipswitch
nop
CheckAutoplayDipswitchEnd:
.org 0x8007f914
; Disable random drops
nop
.org 0x8007f8a8
; Disable randomness of timing
li v0, 0
nop
nop
.org 0x8007f8f4
; Change allowable timing window of autoplay
; Default is 16 which will result in a mix of perfect and marvelous judgements
; 2 may possibly be a bit tight on real hardware, but MAX 300 is a pain and you get 1 or 2 goods at the end with a value of 4
slti v0, s2, AUTOPLAY_TIMING

85
src/main.asm Normal file
View File

@ -0,0 +1,85 @@
.psx
.open "build/GAME.DAT", 0x80010000 - 0x60000 - 0x800
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Force full unlocks (songs and characters)
; 0 = Off
; 1 = On
.definelabel FORCE_UNLOCK, 1
; Transforms 5th Mix into 5th Mix Solo
; 0 = Off
; 1 = On
.definelabel SOLO_MODE, 1
; Use solo cabinet I/O
; Still experimental so may not work properly on real hardware
; 0 = Off
; 1 = On
; Can be defined in the ASM directly but it's easier for the build scripts to pass "-definelabel SOLO_IO 1" to armips
;.definelabel SOLO_IO, 1
; Autoplay Enabled
; When this option is enabled, dipswitch 1 can be used to toggle autoplay
; 0 = Off
; 1 = On
.definelabel AUTOPLAY_ENABLED, 1
; Autoplay Timing Window
; Default value is 16 which results in mixture of perfect and marvelous judgement.
; Anything tigther than 2 will cause notes to be missed.
.definelabel AUTOPLAY_TIMING, 2
; Disable announcer comments during song
; 0 = Off
; 1 = On
.definelabel DISABLE_ANNOUNCER, 0
; Disable cheering during song
; 0 = Off
; 1 = On
.definelabel DISABLE_CHEERING, 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.if DISABLE_ANNOUNCER == 1
.org 0x80080c50
; Disable random announcer comments
nop
.org 0x80080204
; Disable combo is continuing
nop
.endif
.if DISABLE_CHEERING == 1
.org 0x80080de8
; Disable cheering
nop
.endif
.if FORCE_UNLOCK == 1
.org 0x8009bea0
li v0, 0xffffffff
jr ra
nop
.endif
.if AUTOPLAY_ENABLED == 1
.include "src/autoplay.asm"
.endif
.if SOLO_MODE == 1
.include "src/solo.asm"
.endif
.ifdef SOLO_IO
.include "src/solo_io.asm"
.endif
.close

347
src/solo.asm Normal file
View File

@ -0,0 +1,347 @@
; TODO: Remap solo scores to save as doubles?
.org 0x8002b8e0
is_1p_panel_left_pressed:
.org 0x8002b904
is_1p_panel_right_pressed:
.org 0x8002b808
is_1p_start_pressed:
.org 0x80041744
draw_text:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Custom code area
.org 0x80038ce4
SoloRatingPatch:
li v1, 2
move v0, a0
bne a1, v1, SoloRatingNormalPath
nop
j SoloRatingNormalRead
addi v0, 0x04
SoloRatingNormalPath:
sll v0, a1, 0x2
addu v0, a0, v0
SoloRatingNormalRead:
lh v0, 0x10(v0)
sll v1, a2, 0x2
j SoloRatingPatchEnd
nop
InputTestMenu:
; Up-Left
clear a0
li a2,-0x1e
li v0, UpLeftText
sw s1,0x10(sp)
sw v0,0x14(sp)
lw a1,0x0(s0)
jal draw_text
li a3,0x1000
li a2,-0x1e
li a3,0x1000
lw a0,0x120(sp)
nop
srl a0,a0,16
andi a0,a0,0x0001
sltu a0,zero,a0
li v0,0x80012d2c
sll a1,a0,4
add v0, a1
lw a1,0x0(s0)
sw s1,0x10(sp)
sw v0,0x14(sp)
sll a0,a0,0x1
addiu a1,a1,0x48
jal draw_text
nop
; Up-Right
clear a0
li a2,-0x16
li v0, UpRightText
sw s1,0x10(sp)
sw v0,0x14(sp)
lw a1,0x0(s0)
jal draw_text
li a3,0x1000
li a2,-0x16
li a3,0x1000
lw a0,0x120(sp)
nop
srl a0,a0,16
andi a0,a0,0x0002
sltu a0,zero,a0
li v0,0x80012d2c
sll a1,a0,4
add v0, a1
lw a1,0x0(s0)
sw s1,0x10(sp)
sw v0,0x14(sp)
sltu a0,zero,a0
sll a0,a0,0x1
jal draw_text
addiu a1,a1,0x48
jal 0x80029728
nop
j InputTestMenuEnd
nop
UpLeftText:
.asciiz "UP-LEFT"
UpRightText:
.asciiz "UP-RIGHT"
; End custom code area
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.org 0x8001c174
this_machine_is_solo:
; Always return 1 to enable solo machine mode
li v0, 1
.org 0x800909ec
; Allow for selecting 6 panel mode on style select screen
nop
.org 0x8009b400
nop
j SoloRatingPatch
nop
nop
SoloRatingPatchEnd:
.org 0x8002b898
; Disable 2nd player start button during style select screen
jr ra
clear v0
; Use small stage number at top during gameplay
.org 0x800571b0
b 0x800571cc
; Reposition arrows to be centered and evenly spaced around smaller stage number
.org 0x80014110
.dh 0xffac - 7
.dh 0xffe1 - 2
.dh 0xffff
.dh 0x0034 + 5
.dh 0xffc7 - 5
.dh 0xff5f
.dh 0x0019 + 3
.dh 0xff5f
.org 0x80014120
.dh 0xffc2 - 3
.dh 0xffe1 - 2
.dh 0xffff
.dh 0x001e + 1
; Change graphics for 4 panel and 6 panel mode on style select screen
.org 0x80016144
; 4 Panel/Single
.dh 0x0001 ; 4 Panel/Single flag
.dh 0x002c + 0x63 ; Layer absolute x
.dh 0x00fb ; Layer absolute y
.dh 0x001c ; Character relative x
.dh 0x0033 ; Character relative y
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.db 0x04 ; Stages bubble design + tail direction
.db 0x78 ; More bubble relative y??
.dh 0xffbe ; Bubble relative x
.dh 0x000e ; Bubble relative y
; 6 Panel
.dh 0x000a ; 6 Panel
.dh 0x00cd + 0x63
.dh 0x00c0
.dh 0x001c
.dh 0x0051
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.db 0x03
.db 0x5a
.dh 0x0056
.dh 0x0008
; Disable last entry
.dh 0 ; Disabled
.dh 0xff00 ; Send off to outer space
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.dh 0
.db 0
.db 0
.dh 0
.dh 0
; Fix solo mode graphic going dim
.org 0x800900dc
nop
; Don't show "insert more coins to select other modes" message
.org 0x8008e9e0
nop
; Don't show secondary description text
.org 0x8008e994
j 0x8008ea04
; Force 6 panel description when 4 panel isn't selected
.org 0x8008e944
li v1, 0x0a
.org 0x8008e96c
li t1, 0x02
; Load "DOUBLE" text instead of "SINGLE" for 6 panel mode
.org 0x8008e76c
li v1, 0x0a
; Center bottom text on style select screen
.org 0x80084398
addiu s0, s0, -0xa0
; Disable right player side text during free play mode
.org 0x80084438
nop
; Make the character select screen show all 14 characters instead of 7 for 1P
.org 0x80091ed4
li v0, 0x0e
; Don't show "NOT ATTEND" image on 2P side
.org 0x80091564
b 0x80091dc0
; Change character update region width
; 2P characters leave the white glow when moving the cursor on the background because
; that region isn't being updated
.org 0x80092268
li s7, 0x240
.org 0x80092398
li s8, 0x240
; Don't draw 2P side character name
.org 0x80090ed8
addiu a2, v0, 0xcb
li t1, 0
; Disable static image when character not selected
.org 0x800910c8
li s8, 0x180
; Remove black border triangles from character image area for 2P side
.org 0x80090fcc
li s8, 0x180
; Use 2P select icon instead of 1P icon at top of screen (required for image edits)
.org 0x80083d04
li v0, 0x0a
.org 0x80083d10
addiu v1, v1, 3
; Fix I/O test menu exit key combo
.org 0x8003c314
jal is_1p_panel_left_pressed
.org 0x8003c324
jal is_1p_panel_right_pressed
clear a0
.org 0x80012db8
.asciiz " HOLD 1P LEFT +"
.org 0x80012dd0
.asciiz "PRESS 1P RIGHT = EXIT"
; Fix start button during free player
.org 0x8001f984
jal is_1p_start_pressed
.org 0x8001f994
jal is_1p_start_pressed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; I/O Test Menu
.org 0x8003bf08
j InputTestMenu
InputTestMenuEnd:
; Fix centering of text
.org 0x8003bad4
li v0, -0x60
.org 0x8003badc
li v0, 0x30
.org 0x8003c00c
; Don't display 2P side buttons
nop
.org 0x8003bf14
; Select L Text
li a2, 0xfffffff2
.org 0x8003bf34
; Select L ON/OFF
li a2, 0xfffffff2
.org 0x8003bf64
; Select R Text
li a2, 0xfffffffa
.org 0x8003bf84
; Select R ON/OFF
li a2, 0xfffffffa
.org 0x8003bfbc
; Start Text
li a2, 0x02
.org 0x8003bfdc
; Start ON/OFF
li a2, 0x02
.org 0x8003c140
; Service Switch Text
li a2, 0x12
.org 0x8003c164
; Service Switch Text
li a2, 0x12
.org 0x8003c194
; Coin Mech ON/OFF
li a2, 0x1a
.org 0x8003c1b4
; Coin Mech ON/OFF
li a2, 0x1a
.org 0x8003c1f4
; Coin Mech ON/OFF
li a2, 0x2a
.org 0x8003c244
; Coin Mech ON/OFF
li a2, 0x2a

44
src/solo_io.asm Normal file
View File

@ -0,0 +1,44 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Custom code area
; Shares a very close space to the code location used in solo.asm
; so be careful that the two don't collide.
.org 0x80038e44
PanelIoFix:
nor a1, zero, a1
nor a2, zero, a2
nor v0, zero, v0
; This masks out unwanted inputs
andi a1, 0xffff8f1f
; This converts the select l/r button inputs
andi v1, a2, 0x200
sll v1, 4
or a1, v1
xor a2, v1
andi v1, v0, 0x200
sll v1, 5
or a1, v1
xor v0, v1
; Had to reuse v1 to not mess up any other registers, so read the value again
lhu v1, 0x1f400004
nop
nor v1, zero, v1
j PanelIoFixEnd
nop
; End custom code area
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.org 0x800b0424
j PanelIoFix
nop
nop
nop
nop
PanelIoFixEnd:

5
step1_extract.bat Normal file
View File

@ -0,0 +1,5 @@
if not exist data_raw mkdir data_raw
tools\sys573tool.exe --mode dump --input data_source --output data_raw --key DDR5 --type ddr --input-filenames tools\ddr5th_filenames.json
pause

4
step1_extract.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p data_raw
python3 tools/py/dump_sys573_gamefs.py --input data_source --output data_raw --key DDR5 --type ddr --input-filenames tools/ddr5th_filenames.json

11
step2_make.bat Normal file
View File

@ -0,0 +1,11 @@
if not exist build mkdir build
xcopy /Y /E /H /C data_source\* build
if exist build\deleteme.txt del build\deleteme.txt
tools\sys573tool.exe --mode build --input data_raw --input-modified-list data_modified\modified.json --base data_source --key DDR5 --output build --patch-dir data_modified
tools\thirdparty\armips.exe src\main.asm
tools\sys573tool.exe --mode checksum --input build\GAME.DAT build\CARD.DAT --output build
tools\thirdparty\mkisofs.exe -d -o ddr5thsolo.iso "build"
pause

15
step2_make.sh Normal file
View File

@ -0,0 +1,15 @@
#!/bin/bash
mkdir -p build
cp -r data_source/* build
rm -f build/deleteme.txt
python3 tools/py/build_sys573_gamefs.py --input data_raw --input-modified-list data_modified/modified.json --base data_source --key DDR5 --output build --patch-dir data_modified
armips src/main.asm
python3 tools/py/calc_checksum.py --input build/GAME.DAT build/CARD.DAT --output build
mkisofs -d -o ddr5thsolo.iso "build"

11
step2_make_soloio.bat Normal file
View File

@ -0,0 +1,11 @@
if not exist build mkdir build
xcopy /Y /E /H /C data_source\* build
if exist build\deleteme.txt del build\deleteme.txt
tools\sys573tool.exe --mode build --input data_raw --input-modified-list data_modified\modified.json --base data_source --key DDR5 --output build --patch-dir data_modified
tools\thirdparty\armips.exe -definelabel SOLO_IO 1 src\main.asm
tools\sys573tool.exe --mode checksum --input build\GAME.DAT build\CARD.DAT --output build
tools\thirdparty\mkisofs.exe -d -o ddr5thsolo_soloio.iso "build"
pause

14
step2_make_soloio.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
mkdir -p build
cp -r data_source/* build
rm -f build/deleteme.txt
python3 tools/py/build_sys573_gamefs.py --input data_raw --input-modified-list data_modified/modified.json --base data_source --key DDR5 --output build --patch-dir data_modified
armips -definelabel SOLO_IO 1 src/main.asm
python3 tools/py/calc_checksum.py --input build/GAME.DAT build/CARD.DAT --output build
mkisofs -d -o ddr5thsolo_soloio.iso "build"

674
tools/ddr5th_filenames.json Normal file
View File

@ -0,0 +1,674 @@
[
"boot/checksum.dat",
"boot/config.dat",
"boot/psx.bin",
"data/bpct/blue/aba_blue.cmt",
"data/bpct/blue/bba_blue.cmt",
"data/bpct/blue/c8c_blue.cmt",
"data/bpct/blue/d8c_blue.cmt",
"data/bpct/blue/eba_blue.cmt",
"data/bpct/blue/f9a_blue.cmt",
"data/bpct/blue/g99_blue.cmt",
"data/bpct/blue/h97_blue.cmt",
"data/bpct/blue/i87_blue.cmt",
"data/bpct/blue/jd7_blue.cmt",
"data/bpct/gren/a11_gren.cmt",
"data/bpct/gren/b42_gren.cmt",
"data/bpct/gren/c87_gren.cmt",
"data/bpct/gren/d87_gren.cmt",
"data/bpct/gren/eba_gren.cmt",
"data/bpct/gren/f99_gren.cmt",
"data/bpct/gren/g99_gren.cmt",
"data/bpct/gren/h97_gren.cmt",
"data/bpct/gren/i87_gren.cmt",
"data/bpct/gren/j9c_gren.cmt",
"data/bpct/redd/a87_redd.cmt",
"data/bpct/redd/b8c_redd.cmt",
"data/bpct/redd/cd6_redd.cmt",
"data/bpct/redd/dd6_redd.cmt",
"data/bpct/redd/eba_redd.cmt",
"data/bpct/redd/f99_redd.cmt",
"data/bpct/redd/g99_redd.cmt",
"data/bpct/redd/h97_redd.cmt",
"data/bpct/redd/i97_redd.cmt",
"data/bpct/redd/jd7_redd.cmt",
"data/bpct/yelo/a87_yelo.cmt",
"data/bpct/yelo/b8c_yelo.cmt",
"data/bpct/yelo/cd6_yelo.cmt",
"data/bpct/yelo/dd6_yelo.cmt",
"data/bpct/yelo/eba_yelo.cmt",
"data/bpct/yelo/f99_yelo.cmt",
"data/bpct/yelo/g99_yelo.cmt",
"data/bpct/yelo/h97_yelo.cmt",
"data/bpct/yelo/i97_yelo.cmt",
"data/bpct/yelo/jd7_yelo.cmt",
"data/chara/chara.lst",
"data/chara/chara.pos",
"data/chara/inst_d/inst_d.cmt",
"data/chara/inst_d/inst_d.lst",
"data/chara/inst_d/inst_d.pos",
"data/chara/inst_d/inst_d.tmd",
"data/chara/inst_s/inst_s.cmt",
"data/chara/inst_s/inst_s.lst",
"data/chara/inst_s/inst_s.pos",
"data/chara/inst_s/inst_s.tmd",
"data/fpga/fpga_mp3.bin",
"data/gpct/dance/arroe_16.cmt",
"data/gpct/dance/dangr_16.cmt",
"data/gpct/dance/endgs_25.cmt",
"data/gpct/dance/gauge_25.cmt",
"data/gpct/dance/gaugw_25.cmt",
"data/gpct/dance/gmob_25.cmt",
"data/gpct/dance/gover_25.cmt",
"data/gpct/dance/gread_25.cmt",
"data/gpct/dance/hgove_25.cmt",
"data/gpct/dance/mfrm_25.cmt",
"data/gpct/demo/btile_25.cmt",
"data/gpct/demo/cgchk_25.cmt",
"data/gpct/demo/endin_25.cmt",
"data/gpct/demo/endob_25.cmt",
"data/gpct/demo/insb1_16.cmt",
"data/gpct/demo/insb2_16.cmt",
"data/gpct/demo/insb3_16.cmt",
"data/gpct/demo/inst4_16.cmt",
"data/gpct/demo/inst5_16.cmt",
"data/gpct/demo/klogo_16.cmt",
"data/gpct/demo/obk_16.cmt",
"data/gpct/demo/oobj_25.cmt",
"data/gpct/demo/padv_16.cmt",
"data/gpct/demo/scrbk_16.cmt",
"data/gpct/demo/scrob_25.cmt",
"data/gpct/result/rslbk_16.cmt",
"data/gpct/result/rsldl_25.cmt",
"data/gpct/result/rslob_25.cmt",
"data/gpct/select/albm0_25.cmt",
"data/gpct/select/albm1_25.cmt",
"data/gpct/select/chr01_25.cmt",
"data/gpct/select/chr02_25.cmt",
"data/gpct/select/chr03_25.cmt",
"data/gpct/select/chr04_25.cmt",
"data/gpct/select/chr05_25.cmt",
"data/gpct/select/chr06_25.cmt",
"data/gpct/select/chr07_25.cmt",
"data/gpct/select/chr08_25.cmt",
"data/gpct/select/chr09_25.cmt",
"data/gpct/select/chr10_25.cmt",
"data/gpct/select/chr11_25.cmt",
"data/gpct/select/chr12_25.cmt",
"data/gpct/select/chr13_25.cmt",
"data/gpct/select/chr14_25.cmt",
"data/gpct/select/chrna_25.cmt",
"data/gpct/select/cslbk_16.cmt",
"data/gpct/select/cslic_25.cmt",
"data/gpct/select/cslob_25.cmt",
"data/gpct/select/msbpm_16.cmt",
"data/gpct/select/mslal_16.cmt",
"data/gpct/select/mslbk_16.cmt",
"data/gpct/select/mslob_25.cmt",
"data/gpct/select/mslta_25.cmt",
"data/gpct/select/msltt_16.cmt",
"data/gpct/select/selcm_16.cmt",
"data/gpct/select/seldw_16.cmt",
"data/gpct/select/selic_25.cmt",
"data/gpct/select/seltr_16.cmt",
"data/gpct/select/selup_16.cmt",
"data/gpct/select/sslbk_16.cmt",
"data/gpct/select/ssldf_mm.cmt",
"data/gpct/select/ssldm_mm.cmt",
"data/gpct/select/sslob_25.cmt",
"data/gpct/select/sslop_25.cmt",
"data/lang/japa/blogo_16.cmt",
"data/lang/japa/botan_25.cmt",
"data/lang/japa/caut_25.cmt",
"data/lang/japa/cstxt_25.cmt",
"data/lang/japa/hbota_25.cmt",
"data/lang/japa/hjoin_25.cmt",
"data/lang/japa/hlink_25.cmt",
"data/lang/japa/hmcme_16.cmt",
"data/lang/japa/inst0_16.cmt",
"data/lang/japa/inst1_16.cmt",
"data/lang/japa/inst2_16.cmt",
"data/lang/japa/inst3_16.cmt",
"data/lang/japa/lhowt_25.cmt",
"data/lang/japa/ljoin_25.cmt",
"data/lang/japa/mcmes_16.cmt",
"data/lang/japa/mstxt_25.cmt",
"data/lang/japa/oblog_16.cmt",
"data/lang/japa/ocd_25.cmt",
"data/lang/japa/oklog_16.cmt",
"data/lang/japa/otitl_25.cmt",
"data/lang/japa/otosh_25.cmt",
"data/lang/japa/owarn_16.cmt",
"data/lang/japa/showt_25.cmt",
"data/lang/japa/ssjoi_25.cmt",
"data/lang/japa/sstxt_25.cmt",
"data/lang/japa/title_25.cmt",
"data/lang/japa/warn_16.cmt",
"data/mdb/abso/abso_bk.cmt",
"data/mdb/abso/abso_nm.cmt",
"data/mdb/abso/abso_ta.cmt",
"data/mdb/abso/all.csq",
"data/mdb/abys/abys_bk.cmt",
"data/mdb/abys/abys_nm.cmt",
"data/mdb/abys/abys_ta.cmt",
"data/mdb/abys/all.csq",
"data/mdb/adre/adre_bk.cmt",
"data/mdb/adre/adre_nm.cmt",
"data/mdb/adre/adre_ta.cmt",
"data/mdb/adre/all.csq",
"data/mdb/agai/agai_bk.cmt",
"data/mdb/agai/agai_nm.cmt",
"data/mdb/agai/agai_ta.cmt",
"data/mdb/agai/all.csq",
"data/mdb/alla/all.csq",
"data/mdb/alla/alla_bk.cmt",
"data/mdb/alla/alla_nm.cmt",
"data/mdb/alla/alla_ta.cmt",
"data/mdb/baby/all.csq",
"data/mdb/baby/baby_bk.cmt",
"data/mdb/baby/baby_nm.cmt",
"data/mdb/baby/baby_ta.cmt",
"data/mdb/beto/all.csq",
"data/mdb/beto/beto_bk.cmt",
"data/mdb/beto/beto_nm.cmt",
"data/mdb/beto/beto_ta.cmt",
"data/mdb/bfor/all.csq",
"data/mdb/bfor/bfor_bk.cmt",
"data/mdb/bfor/bfor_nm.cmt",
"data/mdb/bfor/bfor_ta.cmt",
"data/mdb/bril/all.csq",
"data/mdb/bril/bril_bk.cmt",
"data/mdb/bril/bril_nm.cmt",
"data/mdb/bril/bril_ta.cmt",
"data/mdb/brok/all.csq",
"data/mdb/brok/brok_bk.cmt",
"data/mdb/brok/brok_nm.cmt",
"data/mdb/brok/brok_ta.cmt",
"data/mdb/bumb/all.csq",
"data/mdb/bumb/bumb_bk.cmt",
"data/mdb/bumb/bumb_nm.cmt",
"data/mdb/bumb/bumb_ta.cmt",
"data/mdb/burn/all.csq",
"data/mdb/burn/burn_bk.cmt",
"data/mdb/burn/burn_nm.cmt",
"data/mdb/burn/burn_ta.cmt",
"data/mdb/butt/all.csq",
"data/mdb/butt/butt_bk.cmt",
"data/mdb/butt/butt_nm.cmt",
"data/mdb/butt/butt_ta.cmt",
"data/mdb/cafe/all.csq",
"data/mdb/cafe/cafe_bk.cmt",
"data/mdb/cafe/cafe_nm.cmt",
"data/mdb/cafe/cafe_ta.cmt",
"data/mdb/capt/all.csq",
"data/mdb/capt/capt_bk.cmt",
"data/mdb/capt/capt_nm.cmt",
"data/mdb/capt/capt_ta.cmt",
"data/mdb/cats/all.csq",
"data/mdb/cats/cats_bk.cmt",
"data/mdb/cats/cats_nm.cmt",
"data/mdb/cats/cats_ta.cmt",
"data/mdb/clim/all.csq",
"data/mdb/clim/clim_bk.cmt",
"data/mdb/clim/clim_nm.cmt",
"data/mdb/clim/clim_ta.cmt",
"data/mdb/cong/all.csq",
"data/mdb/cong/cong_bk.cmt",
"data/mdb/cong/cong_nm.cmt",
"data/mdb/cong/cong_ta.cmt",
"data/mdb/cube/all.csq",
"data/mdb/cube/cube_bk.cmt",
"data/mdb/cube/cube_nm.cmt",
"data/mdb/cube/cube_ta.cmt",
"data/mdb/damd/all.csq",
"data/mdb/damd/damd_bk.cmt",
"data/mdb/damd/damd_nm.cmt",
"data/mdb/damd/damd_ta.cmt",
"data/mdb/dead/all.csq",
"data/mdb/dead/dead_bk.cmt",
"data/mdb/dead/dead_nm.cmt",
"data/mdb/dead/dead_ta.cmt",
"data/mdb/dive/all.csq",
"data/mdb/dive/dive_bk.cmt",
"data/mdb/dive/dive_nm.cmt",
"data/mdb/dive/dive_ta.cmt",
"data/mdb/dluv/all.csq",
"data/mdb/dluv/dluv_bk.cmt",
"data/mdb/dluv/dluv_nm.cmt",
"data/mdb/dluv/dluv_ta.cmt",
"data/mdb/dome/all.csq",
"data/mdb/dome/dome_bk.cmt",
"data/mdb/dome/dome_nm.cmt",
"data/mdb/dome/dome_ta.cmt",
"data/mdb/drop/all.csq",
"data/mdb/drop/drop_bk.cmt",
"data/mdb/drop/drop_nm.cmt",
"data/mdb/drop/drop_ta.cmt",
"data/mdb/dubi/all.csq",
"data/mdb/dubi/dubi_bk.cmt",
"data/mdb/dubi/dubi_nm.cmt",
"data/mdb/dubi/dubi_ta.cmt",
"data/mdb/dxyy/all.csq",
"data/mdb/dxyy/dxyy_bk.cmt",
"data/mdb/dxyy/dxyy_nm.cmt",
"data/mdb/dxyy/dxyy_ta.cmt",
"data/mdb/dyna/all.csq",
"data/mdb/dyna/dyna_bk.cmt",
"data/mdb/dyna/dyna_nm.cmt",
"data/mdb/dyna/dyna_ta.cmt",
"data/mdb/eaty/all.csq",
"data/mdb/eaty/eaty_bk.cmt",
"data/mdb/eaty/eaty_nm.cmt",
"data/mdb/eaty/eaty_ta.cmt",
"data/mdb/ecst/all.csq",
"data/mdb/ecst/ecst_bk.cmt",
"data/mdb/ecst/ecst_nm.cmt",
"data/mdb/ecst/ecst_ta.cmt",
"data/mdb/elec/all.csq",
"data/mdb/elec/elec_bk.cmt",
"data/mdb/elec/elec_nm.cmt",
"data/mdb/elec/elec_ta.cmt",
"data/mdb/endo/all.csq",
"data/mdb/endo/endo_bk.cmt",
"data/mdb/endo/endo_nm.cmt",
"data/mdb/endo/endo_ta.cmt",
"data/mdb/eran/all.csq",
"data/mdb/eran/eran_bk.cmt",
"data/mdb/eran/eran_nm.cmt",
"data/mdb/eran/eran_ta.cmt",
"data/mdb/estm/all.csq",
"data/mdb/estm/estm_bk.cmt",
"data/mdb/estm/estm_nm.cmt",
"data/mdb/estm/estm_ta.cmt",
"data/mdb/ever/all.csq",
"data/mdb/ever/ever_bk.cmt",
"data/mdb/ever/ever_nm.cmt",
"data/mdb/ever/ever_ta.cmt",
"data/mdb/feal/all.csq",
"data/mdb/feal/feal_bk.cmt",
"data/mdb/feal/feal_nm.cmt",
"data/mdb/feal/feal_ta.cmt",
"data/mdb/gain/all.csq",
"data/mdb/gain/gain_bk.cmt",
"data/mdb/gain/gain_nm.cmt",
"data/mdb/gain/gain_ta.cmt",
"data/mdb/getm/all.csq",
"data/mdb/getm/getm_bk.cmt",
"data/mdb/getm/getm_nm.cmt",
"data/mdb/getm/getm_ta.cmt",
"data/mdb/gher/all.csq",
"data/mdb/gher/gher_bk.cmt",
"data/mdb/gher/gher_nm.cmt",
"data/mdb/gher/gher_ta.cmt",
"data/mdb/gimm/all.csq",
"data/mdb/gimm/gimm_bk.cmt",
"data/mdb/gimm/gimm_nm.cmt",
"data/mdb/gimm/gimm_ta.cmt",
"data/mdb/gotc/all.csq",
"data/mdb/gotc/gotc_bk.cmt",
"data/mdb/gotc/gotc_nm.cmt",
"data/mdb/gotc/gotc_ta.cmt",
"data/mdb/hboo/all.csq",
"data/mdb/hboo/hboo_bk.cmt",
"data/mdb/hboo/hboo_nm.cmt",
"data/mdb/hboo/hboo_ta.cmt",
"data/mdb/hboy/all.csq",
"data/mdb/hboy/hboy_bk.cmt",
"data/mdb/hboy/hboy_nm.cmt",
"data/mdb/hboy/hboy_ta.cmt",
"data/mdb/hdam/all.csq",
"data/mdb/hdam/hdam_bk.cmt",
"data/mdb/hdam/hdam_nm.cmt",
"data/mdb/hdam/hdam_ta.cmt",
"data/mdb/heat/all.csq",
"data/mdb/heat/heat_bk.cmt",
"data/mdb/heat/heat_nm.cmt",
"data/mdb/heat/heat_ta.cmt",
"data/mdb/hhav/all.csq",
"data/mdb/hhav/hhav_bk.cmt",
"data/mdb/hhav/hhav_nm.cmt",
"data/mdb/hhav/hhav_ta.cmt",
"data/mdb/hher/all.csq",
"data/mdb/hher/hher_bk.cmt",
"data/mdb/hher/hher_nm.cmt",
"data/mdb/hher/hher_ta.cmt",
"data/mdb/hify/all.csq",
"data/mdb/hify/hify_bk.cmt",
"data/mdb/hify/hify_nm.cmt",
"data/mdb/hify/hify_ta.cmt",
"data/mdb/hotl/all.csq",
"data/mdb/hotl/hotl_bk.cmt",
"data/mdb/hotl/hotl_nm.cmt",
"data/mdb/hotl/hotl_ta.cmt",
"data/mdb/hypn/all.csq",
"data/mdb/hypn/hypn_bk.cmt",
"data/mdb/hypn/hypn_nm.cmt",
"data/mdb/hypn/hypn_ta.cmt",
"data/mdb/hyst/all.csq",
"data/mdb/hyst/hyst_bk.cmt",
"data/mdb/hyst/hyst_nm.cmt",
"data/mdb/hyst/hyst_ta.cmt",
"data/mdb/ibel/all.csq",
"data/mdb/ibel/ibel_bk.cmt",
"data/mdb/ibel/ibel_nm.cmt",
"data/mdb/ibel/ibel_ta.cmt",
"data/mdb/ifyo/all.csq",
"data/mdb/ifyo/ifyo_bk.cmt",
"data/mdb/ifyo/ifyo_nm.cmt",
"data/mdb/ifyo/ifyo_ta.cmt",
"data/mdb/inse/all.csq",
"data/mdb/inse/inse_bk.cmt",
"data/mdb/inse/inse_nm.cmt",
"data/mdb/inse/inse_ta.cmt",
"data/mdb/iton/all.csq",
"data/mdb/iton/iton_bk.cmt",
"data/mdb/iton/iton_nm.cmt",
"data/mdb/iton/iton_ta.cmt",
"data/mdb/iwas/all.csq",
"data/mdb/iwas/iwas_bk.cmt",
"data/mdb/iwas/iwas_nm.cmt",
"data/mdb/iwas/iwas_ta.cmt",
"data/mdb/kher/all.csq",
"data/mdb/kher/kher_bk.cmt",
"data/mdb/kher/kher_nm.cmt",
"data/mdb/kher/kher_ta.cmt",
"data/mdb/kick/all.csq",
"data/mdb/kick/kick_bk.cmt",
"data/mdb/kick/kick_nm.cmt",
"data/mdb/kick/kick_ta.cmt",
"data/mdb/kyhi/all.csq",
"data/mdb/kyhi/kyhi_bk.cmt",
"data/mdb/kyhi/kyhi_nm.cmt",
"data/mdb/kyhi/kyhi_ta.cmt",
"data/mdb/lase/all.csq",
"data/mdb/lase/lase_bk.cmt",
"data/mdb/lase/lase_nm.cmt",
"data/mdb/lase/lase_ta.cmt",
"data/mdb/lbfo/all.csq",
"data/mdb/lbfo/lbfo_bk.cmt",
"data/mdb/lbfo/lbfo_nm.cmt",
"data/mdb/lbfo/lbfo_ta.cmt",
"data/mdb/lcan/all.csq",
"data/mdb/lcan/lcan_bk.cmt",
"data/mdb/lcan/lcan_nm.cmt",
"data/mdb/lcan/lcan_ta.cmt",
"data/mdb/ldyn/all.csq",
"data/mdb/ldyn/ldyn_bk.cmt",
"data/mdb/ldyn/ldyn_nm.cmt",
"data/mdb/ldyn/ldyn_ta.cmt",
"data/mdb/lead/all.csq",
"data/mdb/lead/lead_bk.cmt",
"data/mdb/lead/lead_nm.cmt",
"data/mdb/lead/lead_ta.cmt",
"data/mdb/letb/all.csq",
"data/mdb/letb/letb_bk.cmt",
"data/mdb/letb/letb_nm.cmt",
"data/mdb/letb/letb_ta.cmt",
"data/mdb/lety/all.csq",
"data/mdb/lety/lety_bk.cmt",
"data/mdb/lety/lety_nm.cmt",
"data/mdb/lety/lety_ta.cmt",
"data/mdb/lupi/all.csq",
"data/mdb/lupi/lupi_bk.cmt",
"data/mdb/lupi/lupi_nm.cmt",
"data/mdb/lupi/lupi_ta.cmt",
"data/mdb/mats/all.csq",
"data/mdb/mats/mats_bk.cmt",
"data/mdb/mats/mats_nm.cmt",
"data/mdb/mats/mats_ta.cmt",
"data/mdb/mdb.bin",
"data/mdb/moon/all.csq",
"data/mdb/moon/moon_bk.cmt",
"data/mdb/moon/moon_nm.cmt",
"data/mdb/moon/moon_ta.cmt",
"data/mdb/movi/all.csq",
"data/mdb/movi/movi_bk.cmt",
"data/mdb/movi/movi_nm.cmt",
"data/mdb/movi/movi_ta.cmt",
"data/mdb/mrtt/all.csq",
"data/mdb/mrtt/mrtt_bk.cmt",
"data/mdb/mrtt/mrtt_nm.cmt",
"data/mdb/mrtt/mrtt_ta.cmt",
"data/mdb/musi/all.csq",
"data/mdb/musi/musi_bk.cmt",
"data/mdb/musi/musi_nm.cmt",
"data/mdb/musi/musi_ta.cmt",
"data/mdb/myge/all.csq",
"data/mdb/myge/myge_bk.cmt",
"data/mdb/myge/myge_nm.cmt",
"data/mdb/myge/myge_ta.cmt",
"data/mdb/naha/all.csq",
"data/mdb/naha/naha_bk.cmt",
"data/mdb/naha/naha_nm.cmt",
"data/mdb/naha/naha_ta.cmt",
"data/mdb/nana/all.csq",
"data/mdb/nana/nana_bk.cmt",
"data/mdb/nana/nana_nm.cmt",
"data/mdb/nana/nana_ta.cmt",
"data/mdb/neve/all.csq",
"data/mdb/neve/neve_bk.cmt",
"data/mdb/neve/neve_nm.cmt",
"data/mdb/neve/neve_ta.cmt",
"data/mdb/nigh/all.csq",
"data/mdb/nigh/nigh_bk.cmt",
"data/mdb/nigh/nigh_nm.cmt",
"data/mdb/nigh/nigh_ta.cmt",
"data/mdb/ninz/all.csq",
"data/mdb/ninz/ninz_bk.cmt",
"data/mdb/ninz/ninz_nm.cmt",
"data/mdb/ninz/ninz_ta.cmt",
"data/mdb/nite/all.csq",
"data/mdb/nite/nite_bk.cmt",
"data/mdb/nite/nite_nm.cmt",
"data/mdb/nite/nite_ta.cmt",
"data/mdb/noli/all.csq",
"data/mdb/noli/noli_bk.cmt",
"data/mdb/noli/noli_nm.cmt",
"data/mdb/noli/noli_ta.cmt",
"data/mdb/olic/all.csq",
"data/mdb/olic/olic_bk.cmt",
"data/mdb/olic/olic_nm.cmt",
"data/mdb/olic/olic_ta.cmt",
"data/mdb/onda/all.csq",
"data/mdb/onda/onda_bk.cmt",
"data/mdb/onda/onda_nm.cmt",
"data/mdb/onda/onda_ta.cmt",
"data/mdb/ones/all.csq",
"data/mdb/ones/ones_bk.cmt",
"data/mdb/ones/ones_nm.cmt",
"data/mdb/ones/ones_ta.cmt",
"data/mdb/onet/all.csq",
"data/mdb/onet/onet_bk.cmt",
"data/mdb/onet/onet_nm.cmt",
"data/mdb/onet/onet_ta.cmt",
"data/mdb/only/all.csq",
"data/mdb/only/only_bk.cmt",
"data/mdb/only/only_nm.cmt",
"data/mdb/only/only_ta.cmt",
"data/mdb/onts/all.csq",
"data/mdb/onts/onts_bk.cmt",
"data/mdb/onts/onts_nm.cmt",
"data/mdb/onts/onts_ta.cmt",
"data/mdb/oops/all.csq",
"data/mdb/oops/oops_bk.cmt",
"data/mdb/oops/oops_nm.cmt",
"data/mdb/oops/oops_ta.cmt",
"data/mdb/oose/all.csq",
"data/mdb/oose/oose_bk.cmt",
"data/mdb/oose/oose_nm.cmt",
"data/mdb/oose/oose_ta.cmt",
"data/mdb/pafr/all.csq",
"data/mdb/pafr/pafr_bk.cmt",
"data/mdb/pafr/pafr_nm.cmt",
"data/mdb/pafr/pafr_ta.cmt",
"data/mdb/para2/all.csq",
"data/mdb/para2/para2_bk.cmt",
"data/mdb/para2/para2_nm.cmt",
"data/mdb/para2/para2_ta.cmt",
"data/mdb/peta/all.csq",
"data/mdb/peta/peta_bk.cmt",
"data/mdb/peta/peta_nm.cmt",
"data/mdb/peta/peta_ta.cmt",
"data/mdb/peti/all.csq",
"data/mdb/peti/peti_bk.cmt",
"data/mdb/peti/peti_nm.cmt",
"data/mdb/peti/peti_ta.cmt",
"data/mdb/pevo/all.csq",
"data/mdb/pevo/pevo_bk.cmt",
"data/mdb/pevo/pevo_nm.cmt",
"data/mdb/pevo/pevo_ta.cmt",
"data/mdb/pink/all.csq",
"data/mdb/pink/pink_bk.cmt",
"data/mdb/pink/pink_nm.cmt",
"data/mdb/pink/pink_ta.cmt",
"data/mdb/ponp/all.csq",
"data/mdb/ponp/ponp_bk.cmt",
"data/mdb/ponp/ponp_nm.cmt",
"data/mdb/ponp/ponp_ta.cmt",
"data/mdb/radi/all.csq",
"data/mdb/radi/radi_bk.cmt",
"data/mdb/radi/radi_nm.cmt",
"data/mdb/radi/radi_ta.cmt",
"data/mdb/reme/all.csq",
"data/mdb/reme/reme_bk.cmt",
"data/mdb/reme/reme_nm.cmt",
"data/mdb/reme/reme_ta.cmt",
"data/mdb/rhyt/all.csq",
"data/mdb/rhyt/rhyt_bk.cmt",
"data/mdb/rhyt/rhyt_nm.cmt",
"data/mdb/rhyt/rhyt_ta.cmt",
"data/mdb/righ/all.csq",
"data/mdb/righ/righ_bk.cmt",
"data/mdb/righ/righ_nm.cmt",
"data/mdb/righ/righ_ta.cmt",
"data/mdb/roma/all.csq",
"data/mdb/roma/roma_bk.cmt",
"data/mdb/roma/roma_nm.cmt",
"data/mdb/roma/roma_ta.cmt",
"data/mdb/sana/all.csq",
"data/mdb/sana/sana_bk.cmt",
"data/mdb/sana/sana_nm.cmt",
"data/mdb/sana/sana_ta.cmt",
"data/mdb/seve/all.csq",
"data/mdb/seve/seve_bk.cmt",
"data/mdb/seve/seve_nm.cmt",
"data/mdb/seve/seve_ta.cmt",
"data/mdb/sexy/all.csq",
"data/mdb/sexy/sexy_bk.cmt",
"data/mdb/sexy/sexy_nm.cmt",
"data/mdb/sexy/sexy_ta.cmt",
"data/mdb/shak/all.csq",
"data/mdb/shak/shak_bk.cmt",
"data/mdb/shak/shak_nm.cmt",
"data/mdb/shak/shak_ta.cmt",
"data/mdb/shoo/all.csq",
"data/mdb/shoo/shoo_bk.cmt",
"data/mdb/shoo/shoo_nm.cmt",
"data/mdb/shoo/shoo_ta.cmt",
"data/mdb/sprs/all.csq",
"data/mdb/sprs/sprs_bk.cmt",
"data/mdb/sprs/sprs_nm.cmt",
"data/mdb/sprs/sprs_ta.cmt",
"data/mdb/stil/all.csq",
"data/mdb/stil/stil_bk.cmt",
"data/mdb/stil/stil_nm.cmt",
"data/mdb/stil/stil_ta.cmt",
"data/mdb/stom/all.csq",
"data/mdb/stom/stom_bk.cmt",
"data/mdb/stom/stom_nm.cmt",
"data/mdb/stom/stom_ta.cmt",
"data/mdb/stop/all.csq",
"data/mdb/stop/stop_bk.cmt",
"data/mdb/stop/stop_nm.cmt",
"data/mdb/stop/stop_ta.cmt",
"data/mdb/summ/all.csq",
"data/mdb/summ/summ_bk.cmt",
"data/mdb/summ/summ_nm.cmt",
"data/mdb/summ/summ_ta.cmt",
"data/mdb/sync/all.csq",
"data/mdb/sync/sync_bk.cmt",
"data/mdb/sync/sync_nm.cmt",
"data/mdb/sync/sync_ta.cmt",
"data/mdb/talk/all.csq",
"data/mdb/talk/talk_bk.cmt",
"data/mdb/talk/talk_nm.cmt",
"data/mdb/talk/talk_ta.cmt",
"data/mdb/teng/all.csq",
"data/mdb/teng/teng_bk.cmt",
"data/mdb/teng/teng_nm.cmt",
"data/mdb/teng/teng_ta.cmt",
"data/mdb/tequ/all.csq",
"data/mdb/tequ/tequ_bk.cmt",
"data/mdb/tequ/tequ_nm.cmt",
"data/mdb/tequ/tequ_ta.cmt",
"data/mdb/them/all.csq",
"data/mdb/them/them_bk.cmt",
"data/mdb/them/them_nm.cmt",
"data/mdb/them/them_ta.cmt",
"data/mdb/trib/all.csq",
"data/mdb/trib/trib_bk.cmt",
"data/mdb/trib/trib_nm.cmt",
"data/mdb/trib/trib_ta.cmt",
"data/mdb/twis/all.csq",
"data/mdb/twis/twis_bk.cmt",
"data/mdb/twis/twis_nm.cmt",
"data/mdb/twis/twis_ta.cmt",
"data/mdb/walk/all.csq",
"data/mdb/walk/walk_bk.cmt",
"data/mdb/walk/walk_nm.cmt",
"data/mdb/walk/walk_ta.cmt",
"data/mdb/wild/all.csq",
"data/mdb/wild/wild_bk.cmt",
"data/mdb/wild/wild_nm.cmt",
"data/mdb/wild/wild_ta.cmt",
"data/mdb/xana/all.csq",
"data/mdb/xana/xana_bk.cmt",
"data/mdb/xana/xana_nm.cmt",
"data/mdb/xana/xana_ta.cmt",
"data/mdb/youn/all.csq",
"data/mdb/youn/youn_bk.cmt",
"data/mdb/youn/youn_nm.cmt",
"data/mdb/youn/youn_ta.cmt",
"data/mdb/your/all.csq",
"data/mdb/your/your_bk.cmt",
"data/mdb/your/your_nm.cmt",
"data/mdb/your/your_ta.cmt",
"data/motion/capoera1/capoera1.cmm",
"data/motion/hiphop1/hiphop1.cmm",
"data/motion/hiphop2/hiphop2.cmm",
"data/motion/hopping1/hopping1.cmm",
"data/motion/inst/inst.cmm",
"data/motion/jazz1/jazz1.cmm",
"data/motion/jazz2/jazz2.cmm",
"data/motion/lock1/lock1.cmm",
"data/motion/mhouse1/mhouse1.cmm",
"data/motion/n31/n31.cmm",
"data/motion/normal/normal.cmm",
"data/motion/sino_/sino_.cmm",
"data/motion/soul1/soul1.cmm",
"data/motion/soul2/soul2.cmm",
"data/motion/thouse2/thouse2.cmm",
"data/motion/thouse3/thouse3.cmm",
"data/motion/y11/y11.cmm",
"data/motion/y31/y31.cmm",
"data/mp3/mp3_tab.bin",
"data/tex/rembind.bin",
"data/tex/subbind.bin",
"data/tim/arrow/arros_25.cmt",
"data/tim/arrow/arrow_16.cmt",
"data/tim/nfont/ac0808.cmt",
"data/tim/nfont/ac1112.cmt",
"data/tim/nfont/ac1616.cmt",
"data/tim/nfont/nm1220.cmt",
"data/tim/nfont/nm2020.cmt",
"data/tim/nfont/nm2422.cmt",
"data/tim/nfont/nm3222.cmt",
"data/tim/nfont/sc1216.cmt",
"data/tim/wfont/acxx12.cmt",
"data/tim/wfont/acxx16.cmt",
"data/tim/wfont/acxx20.cmt",
"data/tim/wfont/nmxx32.cmt",
"data/tim/wfont/rexx16.cmt",
"data/tim/wfont/wfont_w.bin",
"soft/s573/aout.exe"
]

View File

@ -0,0 +1,429 @@
import argparse
import copy
import ctypes
import glob
import hashlib
import json
import os
import pathlib
import struct
import string
import comp573
import sum573
def rebuild_checksum_table(cards):
card_sizes = [len(x) // 0x8000 for x in cards]
CHUNK_SIZE = 0x20000
LAST_CHUNK_OFFSET = len(cards[0]) - CHUNK_SIZE
LAST_CHUNK_CHECKSUM_OFFSET = LAST_CHUNK_OFFSET + 0x10
# Set entire checksum.dat section to zero
cards[0] = cards[0][:LAST_CHUNK_CHECKSUM_OFFSET] + bytearray([0] * 0x1ff0) + cards[0][LAST_CHUNK_OFFSET + 0x2000:]
# Calculate checksums for GAME.DAT
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 0, 1)
# Balance out the sums at this point because otherwise the chunk checksum won't match
cards = sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
# Set the real checksum of the last section finally
table_checksum_idx = len(cards[0]) // CHUNK_SIZE
table_checksum_offset = LAST_CHUNK_CHECKSUM_OFFSET + ((table_checksum_idx - 1) * 4)
cards[0][table_checksum_offset:table_checksum_offset+4] = sum573.checksum_chunk(cards[0], LAST_CHUNK_OFFSET, CHUNK_SIZE)
# Add checksums for other DATs now
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 1, len(cards) - 1)
sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
def get_filename_hash(filename, entry):
hash = 0
if 'filename_hash' in entry:
return entry['filename_hash']
if filename.startswith("_output_") and filename.endswith(".bin"):
hash = int(filename.replace("_output_", "").replace(".bin", ""), 16)
return hash
for cidx, c in enumerate(filename):
for i in range(6):
hash = ctypes.c_int(((hash >> 31) & 0x4c11db7) ^ ((hash << 1) | ((ord(c) >> i) & 1))).value
hash &= 0xffffffff
return hash
def encrypt_data(data, input_key):
def calculate_key(input):
key = 0
for c in input.upper():
if c in string.ascii_uppercase:
key -= 0x37
elif c in string.ascii_lowercase:
key -= 0x57
elif c in string.digits:
key -= 0x30
key += ord(c)
return key & 0xff
val = 0x41C64E6D
key1 = (val * calculate_key(input_key)) & 0xffffffff
counter = 0
for idx, c in enumerate(data):
val = ((key1 + counter) >> 5) ^ c
data[idx] = val & 0xff
counter += 0x3039
return data
def get_filetable(input_folder, input_modified_list, patch_dir=""):
entries = []
new_entries = []
if input_modified_list and os.path.exists(input_modified_list):
new_entries += json.load(open(input_modified_list))
metadata_path = os.path.join(input_folder, "_metadata.json")
if os.path.exists(metadata_path):
entries = json.load(open(metadata_path)).get('files', [])
for entry in json.load(open(metadata_path)).get('modified', []):
exists = False
for new_entry in new_entries:
if entry['filename'] == new_entry['filename']:
exists = True
if not exists:
new_entries.append(entry)
new_entry_filenames = []
if new_entries:
for entry in new_entries:
entry['_path'] = os.path.join(input_folder, entry['filename'])
entry['filename_hash'] = get_filename_hash(entry['filename'], entry)
entry['_modified'] = True
if entry.get('patch', None) is not None:
entry['patch'] = os.path.join(patch_dir, entry['patch'])
new_entry_filenames.append(entry['filename'])
if entries:
for entry in entries:
entry['_path'] = os.path.join(input_folder, entry['filename'])
entry['filename_hash'] = get_filename_hash(entry['filename'], entry)
if entry.get('patch', None) is not None:
entry['patch'] = os.path.join(patch_dir, entry['patch'])
# # Free some space by removing any MP3s actually inside the flash card by default
# if entry['filename'].startswith("data/mp3/enc"):
# entry['_free'] = True
# # Free even more space by removing all of the data in the mdb folder besides the mdb.bin
# if entry['filename'].startswith("data/mdb/") and entry['filename'] not in ['data/mdb/mdb.bin']:
# entry['_free'] = True
# Free up the space used by files that will be overwritten
if entry['filename'] in new_entry_filenames:
for entry2 in new_entries:
if entry2['filename'] == entry['filename']:
size = entry['filesize']
if (size % 0x800) != 0:
padding = 0x800 - (size % 0x800)
else:
padding = 0x800
entry2['_orig_filesize'] = entry['filesize'] + padding
break
entry['_free'] = True
return sorted(entries + new_entries, key=lambda x: x['filename_hash'])
def get_data_from_entry(entry):
data = open(os.path.normpath(entry['_path']), "rb").read()
if entry.get('patch', None) is not None:
if entry.get('patch_format') == "bsdiff4":
import bsdiff4
data = bsdiff4.patch(data, open(entry['patch'], "rb").read())
return bytearray(data)
def create_gamedata(entries, base_offset, memory, enc_key, override_edit_section):
# You can modify this to default to unused and you can probably squeeze a little bit more data
# into the cards, but you will almost surely run over some data you shouldn't touch so be careful.
memory_map = [bytearray([1] * len(mem)) for mem in memory] # 0 = unused, 1 = used
memory_map[0][:0x200000] = [1] * 0x200000 # Reserve this section for the program code
memory_map[1][0x18c0000:0x1b66800] = [1] * (0x1b66800 - 0x18c0000) # Reserve this section because it's where system sounds reside (not in actual file table)
if override_edit_section:
memory_map[1][0x1b66800:0x2000000] = [0] * (0x2000000 - 0x1b66800) # Unreserve the space where edit data is normally stored
# Find the data
entries_work = entries[::]
# Mark unmodified data as used and freed data as unused
for entry in entries_work[::]:
if entry.get('_modified', False):
continue
cur_memory = entry['offset']
size = entry['filesize']
if (size % 0x800) != 0:
size += 0x800 - (size % 0x800)
if entry.get('_free', False):
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + size] = [0] * size
else:
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + size] = [1] * size
entries_work.remove(entry)
entries_work = [x for x in entries_work if not x.get('_free', False)]
entries = [x for x in entries if not x.get('_free', False)]
entries_work_priority = [x for x in entries_work if x['filename'] in ['data/mp3/mp3_tab.bin', 'data/mdb/mdb.bin'] or x['filename'].startswith("boot/") or x['filename'].startswith("soft/")]
entries_work = entries_work_priority + [x for x in entries_work if x not in entries_work_priority]
data_hashes = {}
used_addresses = []
cur_memory = 0
# Certain files need to be at specific offsets or else they won't work properly (seemingly, maybe it's a bug with my code somewhere)
# so put any "important" files where they should be for the most part
for entry in entries_work[::]:
if not entry.get('_modified', False):
continue
if not entry['filename'].startswith('boot/') and not entry['filename'].startswith('soft/') and not entry['filename'].startswith('data/fpga/'):
continue
if 'offset' in entry:
cur_memory = entry['offset']
data = get_data_from_entry(entry)
if entry.get('flag_comp', 0) == 1:
data = comp573.encode_lz(data, len(data))
if entry.get('flag_enc', 0) == 1:
data = encrypt_data(data, enc_key)
entry['filesize'] = len(data)
datahash = hashlib.sha1(data).hexdigest()
data_hashes[datahash] = {
'offset': cur_memory,
'filesize': entry['filesize'],
'loc': entry.get('flag_loc', 0),
}
if len(data) > entry['filesize']:
print("Filesize is too large: %08x vs %08x" % (len(data), entry['filesize']))
elif len(data) < entry['filesize']:
entry['filesize'] = len(data)
# Pad with 0xff
data += b'\xff' * (entry['filesize'] - len(data))
size = entry['filesize']
padding = 0
if (size % 0x800) != 0:
padding = 0x800 - (size % 0x800)
else:
padding = 0x800
size += padding
memory[entry.get('flag_loc', 0)][cur_memory + entry['filesize']:cur_memory + entry['filesize'] + padding] = bytearray([0xff] * padding)
memory[entry.get('flag_loc', 0)][cur_memory:cur_memory + entry['filesize']] = data
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + entry['filesize']] = [1] * size
entries_work.remove(entry)
used_addresses.append((entry.get('flag_loc', 0), cur_memory))
# For everything else, just try to find a fitting space in the available areas
entries_len = len(entries_work[::])
for entry_idx, entry in enumerate(entries_work[::]):
if not entry.get('_modified', False):
continue
data = get_data_from_entry(entry)
orig_len = len(data)
if entry.get('flag_comp', 0) == 1:
data = comp573.encode_lz(data, len(data))
if entry.get('flag_enc', 0) == 1:
data = encrypt_data(data, enc_key)
datahash = hashlib.sha1(data).hexdigest()
size = len(data)
padding = 0
if (size % 0x800) != 0:
padding = 0x800 - (size % 0x800)
else:
padding = 0x800
is_dupe = False
if datahash in data_hashes:
cur_memory = data_hashes[datahash]['offset']
loc = data_hashes[datahash]['loc']
is_dupe = True
else:
# This code is not optimized. It sucks but it works.
# The general idea I was trying to implement is to find the first
# string of 0s in the memory map that could fit the size of the data
# padded to the nearest sector (0x800).
# The padding is key because if you can't clear out the sector properly
# then the game has a higher chance of crashing for some reason.
# Possibly due to decompression reading in garbage data as compressed data.
for loc in range(0, len(memory_map)):
loc = entry.get('flag_loc', 1)
cur_memory = 0
while cur_memory < len(memory_map[loc]):
if (cur_memory % 0x800) != 0:
cur_memory += (0x800 - (cur_memory % 0x800))
idx = memory_map[loc].find(0, cur_memory)
if idx == -1:
cur_memory = -1
break
if (idx % 0x800) != 0:
cur_memory = idx + 1
continue
cur_memory = idx
idx = memory_map[loc].find(1, cur_memory, cur_memory + len(data) + padding)
if idx != -1:
cur_memory = idx
if (idx % 0x800) != 0:
cur_memory += 0x800 - (idx % 0x800)
continue
else:
break
if cur_memory > len(memory_map[loc]):
cur_memory = -1
break
if cur_memory > len(memory_map[loc]):
cur_memory = - 1
continue
if loc == 0 and cur_memory + len(data) + padding >= base_offset:
continue
if cur_memory > 0:
break
if cur_memory == -1 or (loc == 0 and cur_memory + len(data) + padding >= base_offset):
print("Couldn't find position for %08x" % len(data), entry)
exit(1)
print("%d / %d: Placing %08x @ %08x in card %d for %s" % (entry_idx, entries_len, len(data), cur_memory, loc, entry['filename']))
if not is_dupe and (loc, cur_memory) in used_addresses:
print("Can't reuse address!")
exit(1)
size += padding
memory[loc][cur_memory:cur_memory + len(data)] = data
memory[loc][cur_memory + len(data):cur_memory + len(data) + padding] = bytearray([0xff] * padding)
memory_map[loc][cur_memory:cur_memory + len(data) + padding] = [1] * size
entry['filesize'] = len(data)
used_addresses.append((loc, cur_memory))
data_hashes[datahash] = {
'offset': cur_memory,
'filesize': entry['filesize'],
'loc': loc,
}
# Update master entries for the file table lazily
for e in entries:
if e == entry:
e['offset'] = cur_memory
entries_work.remove(entry)
entries = sorted(entries, key=lambda x: x['filename_hash'])
for idx, entry in enumerate(entries):
print("%08x" % entry['filename_hash'], entry)
memory[0][base_offset + 0x4000 + (idx * 0x10):base_offset + 0x4000 + ((idx + 1) * 0x10)] = struct.pack("<IHHBBHI", entry['filename_hash'], entry['offset'] // 0x800, entry.get('flag_loc', 0), entry.get('flag_comp', 0), entry.get('flag_enc', 0), entry.get('unk', 0), entry['filesize'])
idx = len(entries)
memory[0][base_offset + 0x4000 + (idx * 0x10):base_offset + 0x4000 + ((idx + 1) * 0x10)] = struct.pack("<IIII", 0, 0, 0, 0)
return memory
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--input', help='Input folder', default=None, required=True)
parser.add_argument('--input-modified-list', help='Input modified list', default=None)
parser.add_argument('--base', help='Base file folder', default=None, required=True)
parser.add_argument('--output', help='Output file', default="output")
parser.add_argument('--key', help='Encryption key', choices=['EXTREME', 'EURO2', 'MAX2', 'DDR5', 'MAMBO'], required=True)
parser.add_argument('--override-edit-section', help='Allows use of end of CARD 2 which would otherwise be used for edit data saved to flash card. REQUIRED ENABLE_EDIT_SECTOR_OVERRIDE ENABLED IN ASM PATCHES!', default=False, action='store_true')
parser.add_argument('--patch-dir', help='Path to use for patch files', default="")
args, _ = parser.parse_known_args()
os.makedirs(args.output, exist_ok=True)
# Settings are specific to DDR Extreme for now
basefileinfo = [("GAME.DAT", 16), ("CARD.DAT", 32)]
base_offset = 0xFE0000
filetable = get_filetable(args.input, args.input_modified_list, args.patch_dir)
card_datas = create_gamedata(filetable, base_offset, [bytearray(open(os.path.join(args.base, info[0]), "rb").read()) for info in basefileinfo], args.key, args.override_edit_section)
card_datas = [bytearray(data)[:basefileinfo[i][1] * 1024 * 1024] for i, data in enumerate(card_datas)]
rebuild_checksum_table(card_datas)
for i, data in enumerate(card_datas):
open(os.path.join(args.output, basefileinfo[i][0]), "wb").write(data)
if __name__ == "__main__":
main()

86
tools/py/calc_checksum.py Normal file
View File

@ -0,0 +1,86 @@
import argparse
import os
import sum573
def rebuild_checksum_table(cards):
card_sizes = [len(x) // 0x8000 for x in cards]
CHUNK_SIZE = 0x20000
LAST_CHUNK_OFFSET = len(cards[0]) - CHUNK_SIZE
LAST_CHUNK_CHECKSUM_OFFSET = LAST_CHUNK_OFFSET + 0x10
# Set entire checksum.dat section to zero
cards[0] = cards[0][:LAST_CHUNK_CHECKSUM_OFFSET] + bytearray([0] * 0x1ff0) + cards[0][LAST_CHUNK_OFFSET + 0x2000:]
# Calculate checksums for GAME.DAT
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 0, 1)
# Balance out the sums at this point because otherwise the chunk checksum won't match
cards = sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
# Set the real checksum of the last section finally
table_checksum_idx = len(cards[0]) // CHUNK_SIZE
table_checksum_offset = LAST_CHUNK_CHECKSUM_OFFSET + ((table_checksum_idx - 1) * 4)
cards[0][table_checksum_offset:table_checksum_offset+4] = sum573.checksum_chunk(cards[0], LAST_CHUNK_OFFSET, CHUNK_SIZE)
# Add checksums for other DATs now
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 1, len(cards) - 1)
sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
def verify_checksums(cards):
card_sizes = [len(x) // 0x8000 for x in cards]
chunk_size = 0x20000
last_chunk_offset = len(cards[0]) - chunk_size
last_chunk_checksum_offset = last_chunk_offset + 0x10
checksums = [int.from_bytes(cards[0][last_chunk_checksum_offset+x:last_chunk_checksum_offset+x+4], 'little') for x in range(0, 0x2000, 4)]
is_valid = True
for real_card_index, card_data in enumerate(cards):
for i in range(0, len(card_data) // chunk_size):
offset = (i * chunk_size) + (0x20 if real_card_index == 0 and i == 0 else 0)
length = chunk_size - (0x20 if real_card_index == 0 and i == 0 else 0)
checksum_bytes = int.from_bytes(sum573.checksum_chunk(card_data, offset, length), 'little')
target_checksum = checksums[i + (sum(card_sizes[:real_card_index]) // 4)]
if checksum_bytes != target_checksum:
print("Sector %d of DAT %d is invalid! %08x vs %08x" % (i, real_card_index, checksum_bytes, target_checksum))
is_valid = False
return is_valid
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--input', help='Input DAT file (list all in order)', nargs='+', required=True)
parser.add_argument('--output', help='Output folder', default="output")
args, _ = parser.parse_known_args()
cards = [bytearray(open(x, "rb").read()) for x in args.input]
for x in args.input:
print(x)
is_valid = verify_checksums(cards)
print("Is checksum table valid?", is_valid)
if not is_valid:
rebuild_checksum_table(cards)
is_valid = verify_checksums(cards)
print("Is checksum table valid?", is_valid)
os.makedirs(args.output, exist_ok=True)
for i, x in enumerate(args.input):
open(os.path.join(args.output, os.path.basename(x)), "wb").write(cards[i])
if __name__ == "__main__":
main()

335
tools/py/comp573.pyx Normal file
View File

@ -0,0 +1,335 @@
# cython: cdivision=True
# distutils: language=c++
from libc.stdint cimport uint8_t
cdef extern from *:
"""
template <typename T>
T* array_new(int n) {
return new T[n];
}
template <typename T>
void array_delete(T* x) {
delete [] x;
}
"""
T* array_new[T](int)
void array_delete[T](T* x)
from libcpp.vector cimport vector
cdef inline int find_data(unsigned char *data, int data_len, unsigned char c, int offset):
while offset < data_len:
if data[offset] == c:
return offset
offset += 1
return -1
cpdef bytearray decode_lz(unsigned char *input_data, int data_len):
cdef bytearray output = bytearray()
cdef int idx = 0
cdef int idx2 = 0
cdef int start_offset = 0
cdef int distance = 0
cdef int control = 0
cdef unsigned char data = 0
cdef int length = 0
while True:
control >>= 1
if (control & 0x100) == 0:
control = input_data[idx] | 0xff00
idx += 1
data = input_data[idx]
idx += 1
if (control & 1) == 0:
output.append(data)
continue
# print("idx: %02x" % idx)
length = -1
if (data & 0x80) == 0:
distance = ((data & 0x03) << 8) | input_data[idx]
length = (data >> 2) + 2
idx += 1
elif (data & 0x40) == 0:
distance = (data & 0x0f) + 1
length = (data >> 4) - 7
# print("%04x %02x %02x" % (control, data, input_data[idx-1]), distance, length)
if length != -1:
start_offset = len(output)
idx2 = 0
while idx2 <= length:
output.append(output[(start_offset - distance) + idx2])
idx2 += 1
continue
if data == 0xff:
break
length = data - 0xb9
# print("%02x %02x" % (data, length))
while length >= 0:
output.append(input_data[idx])
idx += 1
length -= 1
return output
cpdef bytearray encode_lz(unsigned char *data, int data_len):
cdef uint8_t *output = array_new[uint8_t](data_len * 2)
cdef int output_len = 0
cdef int i = 0
cdef int j = 0
cdef int v = 0
cdef int run_length = 0
cdef int last_history_idx = 0
cdef int history_idx = 0
cdef int cmd_offset = 0
cdef int cmd_bit = 0
cdef int offset = 0
cdef list compress_commands = []
cdef list history_commands = []
# Compress runs of previous characters
while offset < data_len:
# Run detection
if output_len > 0 and data[offset] == output[output_len-1]:
c = output[output_len-1]
run_length = 1
while offset + run_length < data_len and data[offset+run_length] == c and run_length < 0x21:
run_length += 1
if run_length > 1:
compress_commands.append([
'repeat',
run_length
])
j = 0
while j < run_length:
output[output_len] = c
output_len += 1
j += 1
offset += run_length
continue
# History check
last_history_idx = max(output_len - 0x400, 0)
history_idx = find_data(output, output_len, data[offset], last_history_idx)
if history_idx != -1:
history_commands.clear()
while True:
history_idx = find_data(output, output_len, data[offset], last_history_idx)
last_history_idx = history_idx + 1
if history_idx == -1:
break
# Check how long we can match the history
i = 1
while offset + i < data_len:
if history_idx + i > output_len and output[-1] == data[offset+i]:
# Copy + repeat
i += 1
elif history_idx + i < output_len and output[history_idx+i] == data[offset+i]:
i += 1
else:
break
history_back_idx = output_len - history_idx
if i in [1, 2, 3, 4] and history_back_idx >= 1 and history_back_idx <= 16:
# Can use a short copy
history_commands.append([
'short_copy',
history_back_idx,
i,
1
])
elif history_back_idx <= 0x3ff and i >= 3 and i <= 0x21:
# Can use a long copy
history_commands.append([
'long_copy',
history_back_idx,
i,
2
])
best_compression = None
for x in history_commands:
if best_compression is None or x[2] - x[3] >= best_compression[2] - best_compression[3]:
best_compression = x
if best_compression and best_compression[2] - best_compression[3] > 0:
compress_commands.append(best_compression)
j = 0
while j < best_compression[2]:
output[output_len] = data[offset]
output_len += 1
offset += 1
j += 1
continue
compress_commands.append([
'raw',
data[offset]
])
output[output_len] = data[offset]
output_len += 1
offset += 1
compress_commands.append(['eof'])
# Step 2: Compress down raw runs
# Step 3: Build down repeat commands
compress_commands2 = []
i = 0
compress_commands_len = len(compress_commands)
while i < compress_commands_len:
if compress_commands[i][0] == 'raw':
run_length = 1
while compress_commands[i+run_length][0] == 'raw':
run_length += 1
if run_length == 1:
compress_commands2.append(compress_commands[i])
i += 1
continue
raw_bulk = bytearray()
for j in range(run_length):
raw_bulk.append(compress_commands[i+j][1])
while len(raw_bulk) > 7:
copy_len = min(len(raw_bulk), 0x46)
chunk = raw_bulk[:copy_len]
raw_bulk = raw_bulk[copy_len:]
compress_commands2.append([
'raw_bulk',
chunk,
len(chunk)
])
while len(raw_bulk) > 0:
copy_len = 1
chunk = raw_bulk[:copy_len]
raw_bulk = raw_bulk[copy_len:]
compress_commands2.append([
'raw',
chunk[0]
])
i += run_length
elif compress_commands[i][0] == 'repeat':
history_back_idx = 1
length = compress_commands[i][1]
while length > 0:
if length in [1, 2, 3, 4] and history_back_idx >= 1 and history_back_idx <= 16:
copy_len = length
# Can use a short copy
compress_commands2.append([
'short_copy',
history_back_idx,
copy_len,
1
])
length -= copy_len
elif history_back_idx <= 0x3ff and length >= 3:
copy_len = min(length, 0x21)
# Can use a long copy
compress_commands2.append([
'long_copy',
history_back_idx,
copy_len,
2
])
length -= copy_len
i += 1
else:
compress_commands2.append(compress_commands[i])
i += 1
output_buffer = bytearray([0])
cmd_offset = 0
cmd_bit = 0
# Step 4: Build actual data now
for x in compress_commands2:
# print("%04x" % len(output_buffer), x)
if cmd_bit == 8:
cmd_offset = len(output_buffer)
output_buffer += int.to_bytes(0, 1, 'little')
cmd_bit = 0
if x[0] == 'raw':
output_buffer += int.to_bytes(x[1], 1, 'little')
elif x[0] == 'eof':
output_buffer[cmd_offset] |= 1 << cmd_bit
output_buffer += int.to_bytes(0xff, 1, 'little')
else:
output_buffer[cmd_offset] |= 1 << cmd_bit
if x[0] == 'raw_bulk':
# 1 + x bytes
output_buffer += int.to_bytes(0xb9 + len(x[1]) - 1, 1, 'little')
output_buffer += x[1]
elif x[0] == 'short_copy':
# 1 byte
v = ((x[2] + 6) << 4) | (x[1] - 1)
output_buffer += int.to_bytes(v, 1, 'little')
elif x[0] == 'long_copy':
# 2 bytes
v = (((x[2] - 3) << 2) << 8) | (x[1] & 0x3ff)
output_buffer += int.to_bytes(v, 2, 'big')
# if len(output_buffer) >= 0x170:
# exit(1)
cmd_bit += 1
return output_buffer

File diff suppressed because it is too large Load Diff

243
tools/py/enc573.pyx Normal file
View File

@ -0,0 +1,243 @@
# cython: cdivision=True
cpdef unsigned int get_filename_hash(unsigned char *filename, unsigned int filename_len):
cdef int hash = 0
cdef unsigned int cidx = 0
cdef unsigned int i = 0
while cidx < filename_len:
i = 0
while i < 6:
hash = ((hash >> 31) & 0x4c11db7) ^ ((hash << 1) | ((filename[cidx] >> i) & 1))
i += 1
cidx += 1
return hash & 0xffffffff
cdef rot(int c):
return ((c >> 7) & 1) | ((c << 1) & 0xff)
cdef int is_bit_set(int value, int n):
return (value >> n) & 1
cdef int bit_swap(int v, int b15, int b14, int b13, int b12, int b11, int b10, int b9, int b8, int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0):
return (is_bit_set(v, b15) << 15) | \
(is_bit_set(v, b14) << 14) | \
(is_bit_set(v, b13) << 13) | \
(is_bit_set(v, b12) << 12) | \
(is_bit_set(v, b11) << 11) | \
(is_bit_set(v, b10) << 10) | \
(is_bit_set(v, b9) << 9) | \
(is_bit_set(v, b8) << 8) | \
(is_bit_set(v, b7) << 7) | \
(is_bit_set(v, b6) << 6) | \
(is_bit_set(v, b5) << 5) | \
(is_bit_set(v, b4) << 4) | \
(is_bit_set(v, b3) << 3) | \
(is_bit_set(v, b2) << 2) | \
(is_bit_set(v, b1) << 1) | \
(is_bit_set(v, b0) << 0)
cpdef bytearray decrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3):
cdef unsigned short v = 0
cdef unsigned short m = 0
cdef unsigned int idx = 0
output_data = bytearray(data_len * 2)
while idx < data_len:
v = (data[idx * 2 + 1] << 8) | data[idx * 2]
m = key1 ^ key2
v = bit_swap(
v,
15 - is_bit_set(m, 0xF),
14 + is_bit_set(m, 0xF),
13 - is_bit_set(m, 0xE),
12 + is_bit_set(m, 0xE),
11 - is_bit_set(m, 0xB),
10 + is_bit_set(m, 0xB),
9 - is_bit_set(m, 0x9),
8 + is_bit_set(m, 0x9),
7 - is_bit_set(m, 0x8),
6 + is_bit_set(m, 0x8),
5 - is_bit_set(m, 0x5),
4 + is_bit_set(m, 0x5),
3 - is_bit_set(m, 0x3),
2 + is_bit_set(m, 0x3),
1 - is_bit_set(m, 0x2),
0 + is_bit_set(m, 0x2)
)
v ^= (is_bit_set(m, 0xD) << 14) ^ \
(is_bit_set(m, 0xC) << 12) ^ \
(is_bit_set(m, 0xA) << 10) ^ \
(is_bit_set(m, 0x7) << 8) ^ \
(is_bit_set(m, 0x6) << 6) ^ \
(is_bit_set(m, 0x4) << 4) ^ \
(is_bit_set(m, 0x1) << 2) ^ \
(is_bit_set(m, 0x0) << 0)
v ^= bit_swap(
key3,
7, 0, 6, 1,
5, 2, 4, 3,
3, 4, 2, 5,
1, 6, 0, 7
)
output_data[idx * 2] = (v >> 8) & 0xff
output_data[idx * 2 + 1] = v & 0xff
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF
if (((key1 >> 15) ^ key1) & 1) != 0:
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF
idx += 1
key3 += 1
return output_data
cpdef bytearray encrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3):
cdef unsigned short v = 0
cdef unsigned short m = 0
cdef unsigned int idx = 0
output_data = bytearray(data_len * 2)
while idx < data_len:
v = (data[idx * 2 + 1] << 8) | data[idx * 2]
m = key1 ^ key2
v = bit_swap(
v,
15 - is_bit_set(m, 0xF),
14 + is_bit_set(m, 0xF),
13 - is_bit_set(m, 0xE),
12 + is_bit_set(m, 0xE),
11 - is_bit_set(m, 0xB),
10 + is_bit_set(m, 0xB),
9 - is_bit_set(m, 0x9),
8 + is_bit_set(m, 0x9),
7 - is_bit_set(m, 0x8),
6 + is_bit_set(m, 0x8),
5 - is_bit_set(m, 0x5),
4 + is_bit_set(m, 0x5),
3 - is_bit_set(m, 0x3),
2 + is_bit_set(m, 0x3),
1 - is_bit_set(m, 0x2),
0 + is_bit_set(m, 0x2)
)
v ^= (is_bit_set(m, 0xD) << 14) ^ \
(is_bit_set(m, 0xC) << 12) ^ \
(is_bit_set(m, 0xA) << 10) ^ \
(is_bit_set(m, 0x7) << 8) ^ \
(is_bit_set(m, 0x6) << 6) ^ \
(is_bit_set(m, 0x4) << 4) ^ \
(is_bit_set(m, 0x1) << 2) ^ \
(is_bit_set(m, 0x0) << 0)
v ^= bit_swap(
key3,
3, 4, 2, 5,
1, 6, 0, 7,
7, 0, 6, 1,
5, 2, 4, 3
)
output_data[idx * 2] = (v >> 8) & 0xff
output_data[idx * 2 + 1] = v & 0xff
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF
if (((key1 >> 15) ^ key1) & 1) != 0:
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF
idx += 1
key3 += 1
return output_data
cpdef bytearray decrypt_ddrsbm(unsigned char *data, int data_len, unsigned short key):
cdef unsigned int output_idx = 0
cdef unsigned int idx = 0
cdef unsigned int even_bit_shift = 0
cdef unsigned int odd_bit_shift = 0
cdef unsigned int is_even_bit_set = 0
cdef unsigned int is_odd_bit_set = 0
cdef unsigned int is_key_bit_set = 0
cdef unsigned int is_scramble_bit_set = 0
cdef unsigned int cur_bit = 0
cdef unsigned int output_word = 0
output_data = bytearray(data_len * 2)
key_data = bytearray(16)
# Generate key data based on input key
key_state = is_bit_set(key, 0x0d) << 15 | \
is_bit_set(key, 0x0b) << 14 | \
is_bit_set(key, 0x09) << 13 | \
is_bit_set(key, 0x07) << 12 | \
is_bit_set(key, 0x05) << 11 | \
is_bit_set(key, 0x03) << 10 | \
is_bit_set(key, 0x01) << 9 | \
is_bit_set(key, 0x0f) << 8 | \
is_bit_set(key, 0x0e) << 7 | \
is_bit_set(key, 0x0c) << 6 | \
is_bit_set(key, 0x0a) << 5 | \
is_bit_set(key, 0x08) << 4 | \
is_bit_set(key, 0x06) << 3 | \
is_bit_set(key, 0x04) << 2 | \
is_bit_set(key, 0x02) << 1 | \
is_bit_set(key, 0x00) << 0
while idx < 8:
key_data[idx * 2] = key_state & 0xff
key_data[idx * 2 + 1] = (key_state >> 8) & 0xff
key_state = (rot(key_state >> 8) << 8) | rot(key_state & 0xff)
idx += 1
while idx < data_len:
output_word = 0
cur_data = (data[(idx * 2) + 1] << 8) | data[(idx * 2)]
cur_bit = 0
while cur_bit < 8:
even_bit_shift = (cur_bit * 2) & 0xff
odd_bit_shift = (cur_bit * 2 + 1) & 0xff
is_even_bit_set = int((cur_data & (1 << even_bit_shift)) != 0)
is_odd_bit_set = int((cur_data & (1 << odd_bit_shift)) != 0)
is_key_bit_set = int((key_data[idx % 16] & (1 << cur_bit)) != 0)
is_scramble_bit_set = int((key_data[(idx - 1) % 16] & (1 << cur_bit)) != 0)
if is_scramble_bit_set == 1:
is_even_bit_set, is_odd_bit_set = is_odd_bit_set, is_even_bit_set
if ((is_even_bit_set ^ is_key_bit_set)) == 1:
output_word |= 1 << even_bit_shift
if is_odd_bit_set == 1:
output_word |= 1 << odd_bit_shift
cur_bit += 1
output_data[output_idx] = (output_word >> 8) & 0xff
output_data[output_idx+1] = output_word & 0xff
output_idx += 2
idx += 1
return output_data

View File

@ -0,0 +1,2 @@
bsdiff4==1.2.1
Cython==0.29.22

6
tools/py/setup.py Normal file
View File

@ -0,0 +1,6 @@
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize(["comp573.pyx", "enc573.pyx", "sum573.pyx"], annotate=True, language_level=3)
)

103
tools/py/sum573.pyx Normal file
View File

@ -0,0 +1,103 @@
# cython: cdivision=True
cpdef inline unsigned short calc_final_sum(unsigned int val):
cdef unsigned short output = val & 0xffff
while val > 0xffff:
val = output + (val >> 16)
output = val
return output
cdef inline (unsigned int, unsigned int) sum_chunk(unsigned char *data, unsigned int offset, unsigned int chunk_size=0x20000):
cdef unsigned int a = 0
cdef unsigned int b = 0
cdef unsigned int i = 0
while i < chunk_size:
a += data[offset+i]
b += data[offset+i+1]
i += 2
return [a, b]
cpdef bytearray checksum_chunk(unsigned char *data, unsigned int offset, unsigned int chunk_size=0x20000):
cdef unsigned int sum1, sum2
cdef unsigned short a = 0
cdef unsigned short b = 0
sum1, sum2 = sum_chunk(data, offset, chunk_size)
a = calc_final_sum(calc_final_sum(sum1) & 0xffff)
b = calc_final_sum(calc_final_sum(sum2) & 0xffff)
return bytearray([a & 0xff, b & 0xff, (a >> 8) & 0xff, (b >> 8) & 0xff])
cpdef list balance_sums(list cards, list card_sizes, unsigned int last_chunk_offset):
cdef unsigned int last_chunk_checksum_offset = last_chunk_offset + 0x10
cdef unsigned int i = 0
cdef unsigned int j = 0
cdef unsigned int a = 0
cdef unsigned int b = 0
cdef unsigned int pad = 0
cdef unsigned char val = 0
cdef unsigned int card_sum = sum(card_sizes)
cards[0][last_chunk_checksum_offset + card_sum:last_chunk_offset + 0x2000] = bytearray([0] * (0x2000 - card_sum - 0x10))
a, b = sum_chunk(cards[0], last_chunk_offset, 0x2000)
while i < 2:
pad = 0x10000 - calc_final_sum(a if i == 0 else b)
j = card_sum
while pad > 0 and j < 0x2000:
val = pad if pad < 0xff else 0xff
cards[0][last_chunk_checksum_offset + j + i] += val
pad -= val
j += 2
i += 1
return cards
cpdef add_checksums(list cards, list card_sizes, unsigned int chunk_size, unsigned int last_chunk_checksum_offset, unsigned int card_start_index, unsigned int card_count):
cdef unsigned int real_card_index = 0
cdef unsigned int i = 0
cdef unsigned int card_offset = 0
cdef unsigned int checksum_offset = 0
cdef unsigned int total_cards = len(cards)
cdef bytearray final_sum
while real_card_index < total_cards:
card_data = cards[real_card_index]
if real_card_index < card_start_index:
# Skip first DAT because it's already been done
real_card_index += 1
continue
if real_card_index - card_start_index > card_count:
break
card_offset = (sum(card_sizes[:real_card_index]) // 4) * real_card_index
i = 0
s = len(card_data) // chunk_size
while i < s:
offset = (i * chunk_size) + (0x20 if real_card_index == 0 and i == 0 else 0)
length = chunk_size - (0x20 if real_card_index == 0 and i == 0 else 0)
final_sum = checksum_chunk(card_data, offset, length)
checksum_offset = last_chunk_checksum_offset + ((i + card_offset) * 4)
cards[0][checksum_offset:checksum_offset+4] = final_sum
i += 1
real_card_index += 1
return cards

23
tools/py/sys573tool.py Normal file
View File

@ -0,0 +1,23 @@
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--mode', help='Operation mode', required=True, choices=['dump', 'build', 'checksum'])
args, _ = parser.parse_known_args()
if args.mode == "dump":
import dump_sys573_gamefs
dump_sys573_gamefs.main()
elif args.mode == "build":
import build_sys573_gamefs
build_sys573_gamefs.main()
elif args.mode == "checksum":
import calc_checksum
calc_checksum.main()
else:
print("Unknown mode:", args.mode)

BIN
tools/sys573tool.exe Normal file

Binary file not shown.

BIN
tools/thirdparty/armips.exe vendored Normal file

Binary file not shown.

BIN
tools/thirdparty/mkisofs.exe vendored Normal file

Binary file not shown.