from pathlib import Path from bidict import bidict def strip_comments(code: str) -> str: result = '' index = 0 for line in code.splitlines(): comment_index = line.find('//') if comment_index == -1: result += line elif comment_index != 0 and not line[:comment_index].isspace(): result += line[:comment_index] index += 1 return result def data_to_notes(data, diff): note_start = -1 note_end = -1 target_found = False diffs = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"} diff_name = diffs.get(diff, "").lower() i = 0 while i < len(data): line = data[i] if line.startswith("COURSE:"): course_value = line[7:].strip().lower() if (course_value.isdigit() and int(course_value) == diff) or course_value == diff_name: target_found = True else: target_found = False if target_found: if line == "#START": note_start = i + 1 elif line == "#END" and note_start != -1: note_end = i break i += 1 notes = [] bar = [] for i in range(note_start, note_end): line = data[i] if line.startswith("#"): bar.append(line) else: if line == ',': if len(bar) == 0 or all(item.startswith('#') for item in bar): bar.append('') notes.append(bar) bar = [] else: item = line.strip(',') bar.append(item) if item != line: notes.append(bar) bar = [] return notes, note_start, note_end def validate_tja(tja_file: Path, check_watermark=False): diffs = bidict({0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}) try: tja_file.read_text(encoding='utf-8-sig') except Exception as e: print(f'Incorrect Encoding: {tja_file}') lines = tja_file.read_text(encoding='utf-8-sig').splitlines() if check_watermark and lines[0] != "//TJADB Project": print(f'Missing Watermark: {tja_file}') tja_data = [cleaned for line in lines if (cleaned := strip_comments(line).strip())] current_diff = None balloon_data = [] for i, line in enumerate(tja_data): if line.startswith('TITLE'): if line.split(':')[1] == 'FIXME': print(f'FIXME name: {tja_file}') elif line.startswith('SUBTITLE:'): subtitle_display = line.split(':')[1][0:2] if subtitle_display != '--' and subtitle_display != '++': print(f'Subtitle Display Type Missing: {tja_file}, Line {i}') elif line.startswith('WAVE:'): wave_path = tja_file.parent / line.split(':')[1] if not wave_path.exists(): print(f'WAVE Path Invalid: {tja_file}, Line {i}') elif wave_path.suffix != '.ogg': print(f'WAVE is not an OGG: {tja_file}, Line {i}') elif line.startswith('DEMOSTART:'): if float(line.split(':')[1]) < 0: print(f'DEMOSTART value is negative: {tja_file}, Line {i}') elif line.startswith('SONGVOL:'): if float(line.split(':')[1]) < 0: print(f'SONGVOL value is negative: {tja_file}, Line {i}') elif line.startswith('SEVOL:'): if float(line.split(':')[1]) < 0: print(f'SEVOL value is negative: {tja_file}, Line {i}') elif line.startswith('BPM:'): if float(line.split(':')[1]) < 0: print(f'BPM value is negative: {tja_file}, Line {i}') elif float(line.split(':')[1]) == 0: print(f'BPM value is 0: {tja_file}') elif line.split(':')[1] == '': print(f'BPM value is missing: {tja_file}, Line {i}') elif line.startswith('BGMOVIE:'): movie_path = tja_file.parent / line.split(':')[1] if not movie_path.exists(): print(f'BGMOVIE Path Invalid: {tja_file}, Line {i}') elif line.startswith('COURSE:'): if line.split(':')[1].isdigit(): if not (0 <= int(line.split(':')[1]) <= 6): print(f'COURSE Name Invalid: {tja_file}, Line {i}') elif line.split(':')[1].lower() not in diffs.inverse: print(f'COURSE Name Invalid: {tja_file}, Line {i}') else: if line.split(':')[1].isdigit(): current_diff = int(line.split(':')[1]) else: current_diff = diffs.inverse[line.split(':')[1].lower()] elif line.startswith('LEVEL:'): if current_diff is None: print(f'LEVEL was specified with no COURSE: {tja_file}, Line {i}') else: if current_diff == 0: if not (1 <= int(line.split(':')[1]) <= 6): print(f'Invalid LEVEL range for COURSE:{current_diff} : {tja_file}, Line {i}') if current_diff == 1: if not (1 <= int(line.split(':')[1]) <= 7): print(f'Invalid LEVEL range for COURSE:{current_diff} : {tja_file}, Line {i}') if current_diff == 2: if not (1 <= int(line.split(':')[1]) <= 8): print(f'Invalid LEVEL range for COURSE:{current_diff} : {tja_file}, Line {i}') if current_diff >= 3: if not (1 <= int(line.split(':')[1]) <= 10): print(f'Invalid LEVEL range for COURSE:{current_diff} : {tja_file}, Line {i}') elif line.startswith('BALLOON:'): if line.split(':')[1] == '': continue for balloon in line.split(':')[1].split(','): if not balloon.isdigit(): print(f'Invalid Balloon: {tja_file}, Line {i}') else: balloon_data.append(int(balloon)) elif line.startswith('SCOREINIT:'): for scoreinit in line.split(':')[1].split(','): if not scoreinit.isdigit(): print(f'Invalid SCOREINIT Value: {tja_file}, Line {i}') elif int(scoreinit) <= 0: print(f'Invalid SCOREINIT Value: {tja_file}, Line {i}') elif line.startswith('SCOREDIFF:'): if not line.split(':')[1].isdigit(): print(f'Invalid SCOREDIFF Value: {tja_file}, Line {i}') elif int(line.split(':')[1]) < 0: print(f'Invalid SCOREDIFF Value: {tja_file}, Line {i}') for i in range(6): notes, note_start, note_end = data_to_notes(tja_data, i) index = 0 balloon_index = 0 in_hold_note = False if notes != []: for bar in notes: for part in bar: if part.startswith('#BPMCHANGE'): bpm = part.split()[1] if float(bpm) <= 0: print(f'BPMCHANGE Invalid: {tja_file}, Line {index+note_start}') elif part.startswith('#MEASURE'): divisor = part.find('/') if float(part[divisor+1:]) == 0: print(f'Time Signature Invalid: {tja_file}, Line {index+note_start}') elif part.startswith('#'): continue else: for note in part: if not note.isdigit(): print(f'Invalid Note Type {note}: {tja_file}, Line {index+note_start}') continue if note == '7' or note == '9': balloon_index += 1 if balloon_index > len(balloon_data): print(f'Balloon Data Missing: {tja_file}, Line {index+note_start}') if note == '8': in_hold_note = False elif 5 <= int(note) <= 9: if in_hold_note: print(f'Drumroll/Balloon Tail Missing or Head Repeated: {tja_file}, Line {index+note_start}') in_hold_note = True index += 1 for file in (Path.cwd()).rglob('*.tja'): validate_tja(file, check_watermark=True) #print(f'{file} is valid')