diff --git a/packages/alphatab/src/importer/Gp3To5Importer.ts b/packages/alphatab/src/importer/Gp3To5Importer.ts index cc74873be..54d828eda 100644 --- a/packages/alphatab/src/importer/Gp3To5Importer.ts +++ b/packages/alphatab/src/importer/Gp3To5Importer.ts @@ -1571,12 +1571,15 @@ export class GpBinaryHelpers { * @returns */ public static gpReadStringByteLength(data: IReadable, length: number, encoding: string): string { + // Fixed-width string field: 1 length byte + `length` data bytes, decoded + // up to min(stringLength, length). Always consumes 1 + length bytes. const stringLength: number = data.readByte(); - const s: string = GpBinaryHelpers.gpReadString(data, stringLength, encoding); - if (stringLength < length) { - data.skip(length - stringLength); - } - return s; + const fieldBytes: Uint8Array = new Uint8Array(length); + data.read(fieldBytes, 0, length); + const effectiveLength: number = Math.max(0, Math.min(stringLength, length)); + const decoded: Uint8Array = new Uint8Array(effectiveLength); + decoded.set(fieldBytes.subarray(0, effectiveLength)); + return IOHelper.toString(decoded, encoding); } } diff --git a/packages/alphatab/test-data/guitarpro5/chord-name-overflow.gp5 b/packages/alphatab/test-data/guitarpro5/chord-name-overflow.gp5 new file mode 100755 index 000000000..8de558027 Binary files /dev/null and b/packages/alphatab/test-data/guitarpro5/chord-name-overflow.gp5 differ diff --git a/packages/alphatab/test/importer/Gp5Importer.test.ts b/packages/alphatab/test/importer/Gp5Importer.test.ts index 4ef957e89..0de09721b 100644 --- a/packages/alphatab/test/importer/Gp5Importer.test.ts +++ b/packages/alphatab/test/importer/Gp5Importer.test.ts @@ -1,4 +1,6 @@ import { Settings } from '@coderline/alphatab/Settings'; +import { GpBinaryHelpers } from '@coderline/alphatab/importer/Gp3To5Importer'; +import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer'; import { type Beat, BeatBeamingMode } from '@coderline/alphatab/model/Beat'; import { Direction } from '@coderline/alphatab/model/Direction'; import { Ottavia } from '@coderline/alphatab/model/Ottavia'; @@ -569,4 +571,28 @@ describe('Gp5ImporterTest', () => { } } }); + + it('chord-name-overflow', async () => { + // GP5 file with a chord name length byte that exceeds the 21-byte field + // (length=32). Pre-fix, gpReadStringByteLength consumed the full 32 bytes, + // mis-aligning the stream and triggering an unbounded readBend loop. + const score = ( + await GpImporterTestHelper.prepareImporterWithFile('guitarpro5/chord-name-overflow.gp5') + ).readScore(); + expect(score.tracks.length).to.equal(1); + expect(score.masterBars.length).to.equal(193); + }); + + it('gpReadStringByteLength caps consumption at field width', () => { + const sentinelByte = 0xca; + const fieldSize = 21; + const overlongHint = 32; + const buffer = ByteBuffer.fromBuffer( + new Uint8Array([overlongHint, ...new Array(fieldSize).fill(0x41), sentinelByte]) + ); + const result = GpBinaryHelpers.gpReadStringByteLength(buffer, fieldSize, 'utf-8'); + expect(result).to.equal('A'.repeat(fieldSize)); + expect(buffer.position).to.equal(1 + fieldSize); + expect(buffer.readByte()).to.equal(sentinelByte); + }); });