Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions packages/alphatab/src/importer/Gp3To5Importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class Gp3To5Importer extends ScoreImporter {

// NOTE: General Midi only defines percussion instruments from 35-81
// Guitar Pro 5 allowed GS extensions (27-34 and 82-87)
// GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
// GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
// (even if they are not correct)
// we can support this properly in future when we allow custom alphaTex articulation definitions
// then we don't need to rely on GP specifics anymore but handle things on export/import
Expand Down Expand Up @@ -1571,12 +1571,13 @@ export class GpBinaryHelpers {
* @returns
*/
public static gpReadStringByteLength(data: IReadable, length: number, encoding: string): string {
const stringLength: number = data.readByte();
const s: string = GpBinaryHelpers.gpReadString(data, stringLength, encoding);
if (stringLength < length) {
data.skip(length - stringLength);
}
return s;
// Fixed-width string field: 1 length byte + `length` data bytes, decoded
// up to min(stringLength, length). Always consumes 1 + length bytes.
const stringLength = data.readByte();
const fieldBytes = new Uint8Array(length);
data.read(fieldBytes, 0, length);
const effectiveLength = Math.min(stringLength, length);
return IOHelper.toString(fieldBytes.subarray(0, effectiveLength), encoding);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/alphatab/src/io/IOHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class IOHelper {
encoding = 'utf-8';
}
const decoder: TextDecoder = new TextDecoder(encoding);
return decoder.decode(data.buffer as ArrayBuffer);
return decoder.decode(data);
}

private static _detectEncoding(data: Uint8Array): string | null {
Expand Down
Binary file not shown.
53 changes: 38 additions & 15 deletions packages/alphatab/test/importer/Gp5Importer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, expect, it } from 'vitest';
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';
Expand Down Expand Up @@ -407,9 +409,7 @@ describe('Gp5ImporterTest', () => {
expect(score.tracks[0].staves[0].bars[1].voices[0].beats[3].preferredBeamDirection).not.toBeTruthy();

// break
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].beamingMode).toBe(
BeatBeamingMode.ForceSplitToNext
);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.ForceSplitToNext);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].invertBeamDirection).toBe(false);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].preferredBeamDirection).not.toBeTruthy();

Expand All @@ -419,9 +419,7 @@ describe('Gp5ImporterTest', () => {
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].invertBeamDirection).toBe(false);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].preferredBeamDirection).not.toBeTruthy();

expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].beamingMode).toBe(
BeatBeamingMode.ForceSplitToNext
);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].beamingMode).toBe(BeatBeamingMode.ForceSplitToNext);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].invertBeamDirection).toBe(false);
expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].preferredBeamDirection).not.toBeTruthy();

Expand All @@ -447,9 +445,7 @@ describe('Gp5ImporterTest', () => {
// invert to down
expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.Auto);
expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].invertBeamDirection).toBe(false);
expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].preferredBeamDirection).toBe(
BeamDirection.Down
);
expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].preferredBeamDirection).toBe(BeamDirection.Down);

// invert to up
expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.Auto);
Expand Down Expand Up @@ -523,18 +519,14 @@ describe('Gp5ImporterTest', () => {
expect(score.style!.headerAndFooter.has(ScoreSubElement.Transcriber)).toBe(false);

expect(score.style!.headerAndFooter.has(ScoreSubElement.Copyright)).toBe(true);
expect(score.style!.headerAndFooter.get(ScoreSubElement.Copyright)!.template).toBe(
'Copyright: %COPYRIGHT%'
);
expect(score.style!.headerAndFooter.get(ScoreSubElement.Copyright)!.template).toBe('Copyright: %COPYRIGHT%');
expect(score.style!.headerAndFooter.get(ScoreSubElement.Copyright)!.isVisible).toBe(true);
expect(score.style!.headerAndFooter.get(ScoreSubElement.Copyright)!.textAlign).toBe(TextAlign.Center);

expect(score.style!.headerAndFooter.has(ScoreSubElement.CopyrightSecondLine)).toBe(true);
expect(score.style!.headerAndFooter.get(ScoreSubElement.CopyrightSecondLine)!.template).toBe('Copyright2');
expect(score.style!.headerAndFooter.get(ScoreSubElement.CopyrightSecondLine)!.isVisible).toBe(true);
expect(score.style!.headerAndFooter.get(ScoreSubElement.CopyrightSecondLine)!.textAlign).toBe(
TextAlign.Center
);
expect(score.style!.headerAndFooter.get(ScoreSubElement.CopyrightSecondLine)!.textAlign).toBe(TextAlign.Center);
});

it('bank', async () => {
Expand Down Expand Up @@ -569,4 +561,35 @@ 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).toBe(1);
expect(score.masterBars.length).toBe(193);
});

it('gpReadStringByteLength caps consumption at field width', () => {
const sentinelByte = 0xca;
const fieldSize = 21;
const overlongHint = 32;

const raw = new Uint8Array(fieldSize + 2);
raw[0] = overlongHint;
for(let i = 0; i < fieldSize; i++) {
raw[i + 1] = 0x41;
}
raw[fieldSize + 1] = sentinelByte;

const buffer = ByteBuffer.fromBuffer(raw);

const result = GpBinaryHelpers.gpReadStringByteLength(buffer, fieldSize, 'utf-8');
expect(result).toBe('A'.repeat(fieldSize));
expect(buffer.position).toBe(1 + fieldSize);
expect(buffer.readByte()).toBe(sentinelByte);
});
});
5 changes: 5 additions & 0 deletions packages/csharp/src/AlphaTab/Core/EcmaScript/TextDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ public string Decode(ArrayBuffer data)
{
return _encoding.GetString(data.Raw, 0, (int)data.ByteLength);
}

public string Decode(Uint8Array data)
{
return _encoding.GetString(data.Buffer.Raw, (int)data.ByteOffset, (int)data.Length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ internal inline fun UByteArray.decodeToDoubleArray(): DoubleArray {
return da
}

@ExperimentalUnsignedTypes
internal inline fun UByteArray.decodeToString(encoding: String): String {
return String(this.toByteArray(), 0, this.size, Charset.forName(encoding))
}


internal inline fun <T : Comparable<T>> List<T>.sort() {
this.sort { a, b ->
a.compareTo(b).toDouble()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package alphaTab.core.ecmaScript

import alphaTab.core.decodeToString
import alphaTab.core.ecmaScript.ArrayBuffer
import java.nio.charset.Charset

internal class TextDecoder(encoding:String) {
private val _encoding:String = encoding

@ExperimentalUnsignedTypes
public fun decode(buffer: ArrayBuffer): String {
return buffer.decodeToString(_encoding)
return String(buffer.toByteArray(), 0, buffer.size, Charset.forName(_encoding))
}

@ExperimentalUnsignedTypes
public fun decode(buffer: Uint8Array): String {
return String(buffer.buffer.toByteArray(), buffer.byteOffset.toInt(), buffer.length.toInt(), Charset.forName(_encoding))
}
}
Loading