Patcher: disclaimer, every mod can be unticked, hot cue docs
- A disclaimer (mods and patcher developed with Claude Code, at your own risk) must be accepted by typing yes, or with --accept, before a build. - Unticking a mod unticks what needs it instead of ticking it back, so code can be unticked; hotcuekeys needs cuecolours. - beatjump starts unticked and is marked with its faults on a player. - README: the hot cue keys (play, gate, set, delete, LEDs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cfa0e85854
commit
bbe784aad2
3 files changed
+119
-16
No files matched your search
+58
-11
@@ -16,6 +16,9 @@ choose, as a new C900NXS.UPD for the player's own USB update.
|
||||
* The new file says 1.32 in its MAIN header so that the player's updater
|
||||
does not skip it as the version it already has; the application itself
|
||||
still reports 1.31 (as arnoldasr's builds do). Only that label differs.
|
||||
* Before anything is built a disclaimer must be accepted: the mods and
|
||||
this patcher were developed with Claude Code (--accept where no one can
|
||||
type).
|
||||
* Nothing here talks to a player. You copy the file to a USB stick and run
|
||||
the update on the player yourself, at your own risk: nothing of this has
|
||||
been tested on hardware except the relabelled stock update (by arnoldasr),
|
||||
@@ -36,8 +39,11 @@ import upd # noqa: E402
|
||||
|
||||
DEFAULT_REPO = os.path.join(os.path.dirname(HERE), 'CDJ900NXSVirtual')
|
||||
UPSTREAM_MODS = ('code', 'utility', 'overview')
|
||||
NEEDS = {'utility': 'code'} # the PHASE METER switch chooses the code mod's meter
|
||||
NEEDS = {'utility': 'code', # the PHASE METER switch chooses the code mod's meter
|
||||
'hotcuekeys': 'cuecolours'} # the hot cues the keys play are read by cuecolours
|
||||
LABEL = '1.32'
|
||||
# not ticked at the start of the list, and why
|
||||
KNOWN_FAULTS = {'beatjump': 'known faults on a player (jumps off the grid, "file corrupted" after a few jumps)'}
|
||||
|
||||
|
||||
def load_repo(repo):
|
||||
@@ -60,6 +66,8 @@ def available(patcher, repo):
|
||||
or os.path.join(repo, 'mods', 'cdj900nxs', 'upstream'))
|
||||
out = []
|
||||
for name, what in patcher.MODS.items():
|
||||
if name in KNOWN_FAULTS:
|
||||
what = '%s [%s]' % (what, KNOWN_FAULTS[name])
|
||||
ok = upstream or name not in UPSTREAM_MODS
|
||||
out.append((name, what, ok, '' if ok else "needs arnoldasr's checkout in mods/cdj900nxs/upstream"))
|
||||
return out
|
||||
@@ -102,9 +110,16 @@ def _keys():
|
||||
return read
|
||||
|
||||
|
||||
def _fix_needs(chosen, mods):
|
||||
for name, need in NEEDS.items():
|
||||
if name in chosen and need not in chosen and any(m[0] == need and m[2] for m in mods):
|
||||
def _toggle(chosen, name, mods):
|
||||
"""Tick or untick one mod: ticking one ticks what it needs, unticking
|
||||
one unticks what needs it (so every mod can be unticked)."""
|
||||
if name in chosen:
|
||||
chosen.discard(name)
|
||||
chosen.difference_update(n for n, need in NEEDS.items() if need == name)
|
||||
else:
|
||||
chosen.add(name)
|
||||
need = NEEDS.get(name)
|
||||
if need and any(m[0] == need and m[2] for m in mods):
|
||||
chosen.add(need)
|
||||
|
||||
|
||||
@@ -115,7 +130,7 @@ def choose(mods):
|
||||
return choose_by_number(mods)
|
||||
if os.name == 'nt':
|
||||
os.system('') # VT sequences on in the Windows console
|
||||
chosen = {m[0] for m in mods if m[2]}
|
||||
chosen = {m[0] for m in mods if m[2] and m[0] not in KNOWN_FAULTS}
|
||||
at = 0
|
||||
lines = len(mods) + 3
|
||||
|
||||
@@ -140,8 +155,7 @@ def choose(mods):
|
||||
elif k == 'down':
|
||||
at = (at + 1) % len(mods)
|
||||
elif k == 'space' and mods[at][2]:
|
||||
chosen ^= {mods[at][0]}
|
||||
_fix_needs(chosen, mods)
|
||||
_toggle(chosen, mods[at][0], mods)
|
||||
elif k == 'all':
|
||||
usable = {m[0] for m in mods if m[2]}
|
||||
chosen = set() if chosen == usable else usable
|
||||
@@ -155,9 +169,9 @@ def choose(mods):
|
||||
|
||||
def choose_by_number(mods):
|
||||
"""For terminals that cannot be read key by key (mintty, pipes)."""
|
||||
chosen = {m[0] for m in mods if m[2]}
|
||||
chosen = {m[0] for m in mods if m[2] and m[0] not in KNOWN_FAULTS}
|
||||
while True:
|
||||
print('\n Mods (all usable ones are ticked):')
|
||||
print('\n Mods (all usable ones without known faults are ticked):')
|
||||
for i, (name, what, ok, why) in enumerate(mods, 1):
|
||||
mark = '[x]' if name in chosen else ('[ ]' if ok else ' - ')
|
||||
print(' %d %s %-11s %s' % (i, mark, name, what if ok else why))
|
||||
@@ -168,11 +182,41 @@ def choose_by_number(mods):
|
||||
raise SystemExit('nothing written')
|
||||
for tok in answer.replace(',', ' ').split():
|
||||
if tok.isdigit() and 1 <= int(tok) <= len(mods) and mods[int(tok) - 1][2]:
|
||||
chosen ^= {mods[int(tok) - 1][0]}
|
||||
_fix_needs(chosen, mods)
|
||||
_toggle(chosen, mods[int(tok) - 1][0], mods)
|
||||
return [m[0] for m in mods if m[0] in chosen]
|
||||
|
||||
|
||||
# --- the disclaimer ------------------------------------------------------------
|
||||
|
||||
DISCLAIMER = '''
|
||||
Please read before anything is built:
|
||||
|
||||
The mods of this patcher (beatjump, cuecolours, hotcuekeys) and the patcher
|
||||
itself were developed with Claude Code, Anthropic's AI coding assistant,
|
||||
by reverse engineering the firmware in an emulator. They have not been
|
||||
reviewed by Pioneer DJ / AlphaTheta and have not been tested on a player
|
||||
except where the README says so. Flashing modified firmware can leave the
|
||||
player unusable. You use the result at your own risk; there is no warranty.
|
||||
'''
|
||||
|
||||
|
||||
def confirm(accepted, ask=input):
|
||||
"""The disclaimer, which must be accepted before a build: by typing yes,
|
||||
or with --accept where no one can type."""
|
||||
print(DISCLAIMER)
|
||||
if accepted:
|
||||
print(' accepted with --accept')
|
||||
return
|
||||
if not sys.stdin.isatty():
|
||||
raise SystemExit('nothing written: accept the disclaimer by typing yes, or with --accept')
|
||||
try:
|
||||
answer = ask(' Type yes to accept and go on: ').strip().lower()
|
||||
except EOFError:
|
||||
answer = ''
|
||||
if answer not in ('yes', 'y', 'ja', 'j'):
|
||||
raise SystemExit('nothing written: the disclaimer was not accepted')
|
||||
|
||||
|
||||
# --- building ------------------------------------------------------------------
|
||||
|
||||
def build(stock, names, patcher, lzss, label=LABEL, log=print):
|
||||
@@ -205,6 +249,8 @@ def main(argv=None):
|
||||
help='no mods: the original application labelled %s, to go back to stock' % LABEL)
|
||||
ap.add_argument('--repo', default=DEFAULT_REPO, help='the CDJ900NXSVirtual checkout (default %(default)s)')
|
||||
ap.add_argument('--list', action='store_true', help='list the mods and exit')
|
||||
ap.add_argument('--accept', action='store_true',
|
||||
help='accept the disclaimer (developed with Claude Code, at your own risk) without asking')
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
patcher, lzss = load_repo(args.repo)
|
||||
@@ -246,6 +292,7 @@ def main(argv=None):
|
||||
print(' no mod chosen: use --stock for the unmodified update')
|
||||
return 1
|
||||
print(' mods: %s' % (', '.join(names) or 'none (stock application)'))
|
||||
confirm(args.accept)
|
||||
|
||||
try:
|
||||
out_raw, app, checks = build(stock, names, patcher, lzss)
|
||||
|
||||
Reference in new issue
Block a user