16d1788356
The mingw linker uses strlen() in some cases, and codeclib.c redefines it, that leads to mingw runtime init to call into our strlen() and then ci->strlen() which of course crashes. Apply the same fix as for malloc and friends: rename the symbol. The codeclib.h include is necessary for normal builds. Change-Id: Ifa85901a3e4a31cc0e10b4b905df348a239d5c99
2273 lines
75 KiB
C
2273 lines
75 KiB
C
/*
|
|
* asap.c - ASAP engine
|
|
*
|
|
* Copyright (C) 2005-2010 Piotr Fusik
|
|
*
|
|
* This file is part of ASAP (Another Slight Atari Player),
|
|
* see http://asap.sourceforge.net
|
|
*
|
|
* ASAP is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* ASAP is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with ASAP; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "codeclib.h"
|
|
#include "asap_internal.h"
|
|
|
|
static byte s_memory[65536];
|
|
static ASAP_ModuleInfo s_module_info;
|
|
|
|
#ifdef ASAP_ONLY_INFO
|
|
|
|
#define GET_PLAYER(name) NULL
|
|
|
|
#else
|
|
|
|
#define GET_PLAYER(name) GET_RESOURCE(name, obx)
|
|
|
|
FUNC(int, ASAP_GetByte, (P(ASAP_State PTR, ast), P(int, addr)))
|
|
{
|
|
switch (addr & 0xff1f) {
|
|
case 0xd014:
|
|
return ast _ module_info->ntsc ? 0xf : 1;
|
|
case 0xd20a:
|
|
case 0xd21a:
|
|
return PokeySound_GetRandom(ast, addr, ast _ cycle);
|
|
case 0xd20e:
|
|
return ast _ irqst;
|
|
case 0xd21e:
|
|
if (ast _ extra_pokey_mask != 0) {
|
|
/* interrupts in the extra POKEY not emulated at the moment */
|
|
return 0xff;
|
|
}
|
|
return ast _ irqst;
|
|
case 0xd20c:
|
|
case 0xd21c:
|
|
case 0xd20f: /* just because some SAP files rely on this */
|
|
case 0xd21f:
|
|
return 0xff;
|
|
case 0xd40b:
|
|
case 0xd41b:
|
|
return ast _ scanline_number >> 1;
|
|
default:
|
|
return dGetByte(addr);
|
|
}
|
|
}
|
|
|
|
FUNC(void, ASAP_PutByte, (P(ASAP_State PTR, ast), P(int, addr), P(int, data)))
|
|
{
|
|
if ((addr >> 8) == 0xd2) {
|
|
if ((addr & (ast _ extra_pokey_mask + 0xf)) == 0xe) {
|
|
ast _ irqst |= data ^ 0xff;
|
|
#define SET_TIMER_IRQ(ch) \
|
|
if ((data & ast _ irqst & ch) != 0) { \
|
|
if (ast _ timer##ch##_cycle == NEVER) { \
|
|
V(int, t) = ast _ base_pokey.tick_cycle##ch; \
|
|
while (t < ast _ cycle) \
|
|
t += ast _ base_pokey.period_cycles##ch; \
|
|
ast _ timer##ch##_cycle = t; \
|
|
if (ast _ nearest_event_cycle > t) \
|
|
ast _ nearest_event_cycle = t; \
|
|
} \
|
|
} \
|
|
else \
|
|
ast _ timer##ch##_cycle = NEVER;
|
|
SET_TIMER_IRQ(1);
|
|
SET_TIMER_IRQ(2);
|
|
SET_TIMER_IRQ(4);
|
|
}
|
|
else
|
|
PokeySound_PutByte(ast, addr, data);
|
|
}
|
|
else if ((addr & 0xff0f) == 0xd40a) {
|
|
if (ast _ cycle <= ast _ next_scanline_cycle - 8)
|
|
ast _ cycle = ast _ next_scanline_cycle - 8;
|
|
else
|
|
ast _ cycle = ast _ next_scanline_cycle + 106;
|
|
}
|
|
else if ((addr & 0xff00) == ast _ module_info->covox_addr) {
|
|
V(PokeyState PTR, pst);
|
|
addr &= 3;
|
|
if (addr == 0 || addr == 3)
|
|
pst = ADDRESSOF ast _ base_pokey;
|
|
else
|
|
pst = ADDRESSOF ast _ extra_pokey;
|
|
pst _ delta_buffer[CYCLE_TO_SAMPLE(ast _ cycle)] += (data - UBYTE(ast _ covox[addr])) << DELTA_SHIFT_COVOX;
|
|
ast _ covox[addr] = CAST(byte) (data);
|
|
}
|
|
else if ((addr & 0xff1f) == 0xd01f) {
|
|
V(int, sample) = CYCLE_TO_SAMPLE(ast _ cycle);
|
|
V(int, delta);
|
|
data &= 8;
|
|
/* NOT data - ast _ consol; reverse to the POKEY sound */
|
|
delta = (ast _ consol - data) << DELTA_SHIFT_GTIA;
|
|
ast _ consol = data;
|
|
ast _ base_pokey.delta_buffer[sample] += delta;
|
|
ast _ extra_pokey.delta_buffer[sample] += delta;
|
|
}
|
|
else
|
|
dPutByte(addr, data);
|
|
}
|
|
|
|
#endif /* ASAP_ONLY_INFO */
|
|
|
|
#define UWORD(array, index) (UBYTE(array[index]) + (UBYTE(array[(index) + 1]) << 8))
|
|
|
|
#ifndef ASAP_ONLY_SAP
|
|
|
|
#ifndef ASAP_ONLY_INFO
|
|
|
|
#ifndef JAVA
|
|
#include "players.h"
|
|
#endif
|
|
|
|
#define CMR_BASS_TABLE_OFFSET 0x70f
|
|
|
|
CONST_ARRAY(byte, cmr_bass_table)
|
|
0x5C, 0x56, 0x50, 0x4D, 0x47, 0x44, 0x41, 0x3E,
|
|
0x38, 0x35, CAST(byte) (0x88), 0x7F, 0x79, 0x73, 0x6C, 0x67,
|
|
0x60, 0x5A, 0x55, 0x51, 0x4C, 0x48, 0x43, 0x3F,
|
|
0x3D, 0x39, 0x34, 0x33, 0x30, 0x2D, 0x2A, 0x28,
|
|
0x25, 0x24, 0x21, 0x1F, 0x1E
|
|
END_CONST_ARRAY;
|
|
|
|
#endif /* ASAP_ONLY_INFO */
|
|
|
|
CONST_ARRAY(int, perframe2fastplay)
|
|
312, 312 / 2, 312 / 3, 312 / 4
|
|
END_CONST_ARRAY;
|
|
|
|
/* Loads native module (anything except SAP) and 6502 player routine. */
|
|
PRIVATE FUNC(abool, load_native, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len), P(RESOURCE, player)))
|
|
{
|
|
#ifndef ASAP_ONLY_INFO
|
|
V(int, player_last_byte);
|
|
#endif
|
|
V(int, music_last_byte);
|
|
V(int, block_len);
|
|
if ((UBYTE(module[0]) != 0xff || UBYTE(module[1]) != 0xff)
|
|
&& (module[0] != 0 || module[1] != 0)) /* some CMC and clones start with zeros */
|
|
return FALSE;
|
|
module_info _ music = UWORD(module, 2);
|
|
#ifndef ASAP_ONLY_INFO
|
|
module_info _ player = UWORD(player, 2);
|
|
player_last_byte = UWORD(player, 4);
|
|
if (module_info _ music <= player_last_byte)
|
|
return FALSE;
|
|
#endif
|
|
music_last_byte = UWORD(module, 4);
|
|
if (module_info _ music <= 0xd7ff && music_last_byte >= 0xd000)
|
|
return FALSE;
|
|
block_len = music_last_byte + 1 - module_info _ music;
|
|
if (6 + block_len != module_len) {
|
|
V(int, info_addr);
|
|
V(int, info_len);
|
|
if (module_info _ type != ASAP_TYPE_RMT || 11 + block_len > module_len)
|
|
return FALSE;
|
|
/* allow optional info for Raster Music Tracker */
|
|
info_addr = UWORD(module, 6 + block_len);
|
|
if (info_addr != module_info _ music + block_len)
|
|
return FALSE;
|
|
info_len = UWORD(module, 8 + block_len) + 1 - info_addr;
|
|
if (10 + block_len + info_len != module_len)
|
|
return FALSE;
|
|
}
|
|
#ifndef ASAP_ONLY_INFO
|
|
if (ast != NULL) {
|
|
COPY_ARRAY(ast _ memory, module_info _ music, module, 6, block_len);
|
|
COPY_ARRAY(ast _ memory, module_info _ player, player, 6, player_last_byte + 1 - module_info _ player);
|
|
}
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE FUNC(void, set_song_duration, (P(ASAP_ModuleInfo PTR, module_info), P(int, player_calls)))
|
|
{
|
|
module_info _ durations[module_info _ songs] = TO_INT(player_calls * module_info _ fastplay * 114000.0 / 1773447);
|
|
module_info _ songs++;
|
|
}
|
|
|
|
#define SEEN_THIS_CALL 1
|
|
#define SEEN_BEFORE 2
|
|
#define SEEN_REPEAT 3
|
|
|
|
PRIVATE FUNC(void, parse_cmc_song, (P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module), P(int, pos)))
|
|
{
|
|
V(int, tempo) = UBYTE(module[0x19]);
|
|
V(int, player_calls) = 0;
|
|
V(int, rep_start_pos) = 0;
|
|
V(int, rep_end_pos) = 0;
|
|
V(int, rep_times) = 0;
|
|
NEW_ARRAY(byte, seen, 0x55);
|
|
INIT_ARRAY(seen);
|
|
while (pos >= 0 && pos < 0x55) {
|
|
V(int, p1);
|
|
V(int, p2);
|
|
V(int, p3);
|
|
if (pos == rep_end_pos && rep_times > 0) {
|
|
for (p1 = 0; p1 < 0x55; p1++)
|
|
if (seen[p1] == SEEN_THIS_CALL || seen[p1] == SEEN_REPEAT)
|
|
seen[p1] = 0;
|
|
rep_times--;
|
|
pos = rep_start_pos;
|
|
}
|
|
if (seen[pos] != 0) {
|
|
if (seen[pos] != SEEN_THIS_CALL)
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
break;
|
|
}
|
|
seen[pos] = SEEN_THIS_CALL;
|
|
p1 = UBYTE(module[0x206 + pos]);
|
|
p2 = UBYTE(module[0x25b + pos]);
|
|
p3 = UBYTE(module[0x2b0 + pos]);
|
|
if (p1 == 0xfe || p2 == 0xfe || p3 == 0xfe) {
|
|
pos++;
|
|
continue;
|
|
}
|
|
p1 >>= 4;
|
|
if (p1 == 8)
|
|
break;
|
|
if (p1 == 9) {
|
|
pos = p2;
|
|
continue;
|
|
}
|
|
if (p1 == 0xa) {
|
|
pos -= p2;
|
|
continue;
|
|
}
|
|
if (p1 == 0xb) {
|
|
pos += p2;
|
|
continue;
|
|
}
|
|
if (p1 == 0xc) {
|
|
tempo = p2;
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (p1 == 0xd) {
|
|
pos++;
|
|
rep_start_pos = pos;
|
|
rep_end_pos = pos + p2;
|
|
rep_times = p3 - 1;
|
|
continue;
|
|
}
|
|
if (p1 == 0xe) {
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
break;
|
|
}
|
|
p2 = rep_times > 0 ? SEEN_REPEAT : SEEN_BEFORE;
|
|
for (p1 = 0; p1 < 0x55; p1++)
|
|
if (seen[p1] == SEEN_THIS_CALL)
|
|
seen[p1] = CAST(byte) p2;
|
|
player_calls += tempo * (module_info _ type == ASAP_TYPE_CM3 ? 48 : 64);
|
|
pos++;
|
|
}
|
|
set_song_duration(module_info, player_calls);
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_cmc, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len), P(int, type), P(RESOURCE, player)))
|
|
{
|
|
V(int, last_pos);
|
|
V(int, pos);
|
|
if (module_len < 0x306)
|
|
return FALSE;
|
|
module_info _ type = type;
|
|
if (!load_native(ast, module_info, module, module_len, player))
|
|
return FALSE;
|
|
#ifndef ASAP_ONLY_INFO
|
|
if (ast != NULL && type == ASAP_TYPE_CMR)
|
|
COPY_ARRAY(ast _ memory, 0x500 + CMR_BASS_TABLE_OFFSET, cmr_bass_table, 0, sizeof(cmr_bass_table));
|
|
#endif
|
|
last_pos = 0x54;
|
|
while (--last_pos >= 0) {
|
|
if (UBYTE(module[0x206 + last_pos]) < 0xb0
|
|
|| UBYTE(module[0x25b + last_pos]) < 0x40
|
|
|| UBYTE(module[0x2b0 + last_pos]) < 0x40)
|
|
break;
|
|
if (module_info _ channels == 2) {
|
|
if (UBYTE(module[0x306 + last_pos]) < 0xb0
|
|
|| UBYTE(module[0x35b + last_pos]) < 0x40
|
|
|| UBYTE(module[0x3b0 + last_pos]) < 0x40)
|
|
break;
|
|
}
|
|
}
|
|
module_info _ songs = 0;
|
|
parse_cmc_song(module_info, module, 0);
|
|
for (pos = 0; pos < last_pos && module_info _ songs < ASAP_SONGS_MAX; pos++)
|
|
if (UBYTE(module[0x206 + pos]) == 0x8f || UBYTE(module[0x206 + pos]) == 0xef)
|
|
parse_cmc_song(module_info, module, pos + 1);
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE FUNC(abool, is_dlt_track_empty, (P(CONST BYTEARRAY, module), P(int, pos)))
|
|
{
|
|
return UBYTE(module[0x2006 + pos]) >= 0x43
|
|
&& UBYTE(module[0x2106 + pos]) >= 0x40
|
|
&& UBYTE(module[0x2206 + pos]) >= 0x40
|
|
&& UBYTE(module[0x2306 + pos]) >= 0x40;
|
|
}
|
|
|
|
PRIVATE FUNC(abool, is_dlt_pattern_end, (P(CONST BYTEARRAY, module), P(int, pos), P(int, i)))
|
|
{
|
|
V(int, ch);
|
|
for (ch = 0; ch < 4; ch++) {
|
|
V(int, pattern) = UBYTE(module[0x2006 + (ch << 8) + pos]);
|
|
if (pattern < 64) {
|
|
V(int, offset) = 6 + (pattern << 7) + (i << 1);
|
|
if ((module[offset] & 0x80) == 0 && (module[offset + 1] & 0x80) != 0)
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
PRIVATE FUNC(void, parse_dlt_song, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module),
|
|
P(BOOLARRAY, seen), P(int, pos)))
|
|
{
|
|
V(int, player_calls) = 0;
|
|
V(abool, loop) = FALSE;
|
|
V(int, tempo) = 6;
|
|
while (pos < 128 && !seen[pos] && is_dlt_track_empty(module, pos))
|
|
seen[pos++] = TRUE;
|
|
module_info _ song_pos[module_info _ songs] = CAST(byte) pos;
|
|
while (pos < 128) {
|
|
V(int, p1);
|
|
if (seen[pos]) {
|
|
loop = TRUE;
|
|
break;
|
|
}
|
|
seen[pos] = TRUE;
|
|
p1 = module[0x2006 + pos];
|
|
if (p1 == 0x40 || is_dlt_track_empty(module, pos))
|
|
break;
|
|
if (p1 == 0x41)
|
|
pos = UBYTE(module[0x2086 + pos]);
|
|
else if (p1 == 0x42)
|
|
tempo = UBYTE(module[0x2086 + pos++]);
|
|
else {
|
|
V(int, i);
|
|
for (i = 0; i < 64 && !is_dlt_pattern_end(module, pos, i); i++)
|
|
player_calls += tempo;
|
|
pos++;
|
|
}
|
|
}
|
|
if (player_calls > 0) {
|
|
module_info _ loops[module_info _ songs] = loop;
|
|
set_song_duration(module_info, player_calls);
|
|
}
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_dlt, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, pos);
|
|
NEW_ARRAY(abool, seen, 128);
|
|
if (module_len == 0x2c06) {
|
|
if (ast != NULL)
|
|
ast _ memory[0x4c00] = 0;
|
|
}
|
|
else if (module_len != 0x2c07)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_DLT;
|
|
if (!load_native(ast, module_info, module, module_len, GET_PLAYER(dlt))
|
|
|| module_info _ music != 0x2000) {
|
|
return FALSE;
|
|
}
|
|
INIT_ARRAY(seen);
|
|
module_info _ songs = 0;
|
|
for (pos = 0; pos < 128 && module_info _ songs < ASAP_SONGS_MAX; pos++) {
|
|
if (!seen[pos])
|
|
parse_dlt_song(module_info, module, seen, pos);
|
|
}
|
|
return module_info _ songs > 0;
|
|
}
|
|
|
|
PRIVATE FUNC(void, parse_mpt_song, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module),
|
|
P(BOOLARRAY, global_seen), P(int, song_len), P(int, pos)))
|
|
{
|
|
V(int, addr_to_offset) = UWORD(module, 2) - 6;
|
|
V(int, tempo) = UBYTE(module[0x1cf]);
|
|
V(int, player_calls) = 0;
|
|
NEW_ARRAY(byte, seen, 256);
|
|
NEW_ARRAY(int, pattern_offset, 4);
|
|
NEW_ARRAY(int, blank_rows, 4);
|
|
NEW_ARRAY(int, blank_rows_counter, 4);
|
|
INIT_ARRAY(seen);
|
|
INIT_ARRAY(blank_rows);
|
|
while (pos < song_len) {
|
|
V(int, i);
|
|
V(int, ch);
|
|
V(int, pattern_rows);
|
|
if (seen[pos] != 0) {
|
|
if (seen[pos] != SEEN_THIS_CALL)
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
break;
|
|
}
|
|
seen[pos] = SEEN_THIS_CALL;
|
|
global_seen[pos] = TRUE;
|
|
i = UBYTE(module[0x1d0 + pos * 2]);
|
|
if (i == 0xff) {
|
|
pos = UBYTE(module[0x1d1 + pos * 2]);
|
|
continue;
|
|
}
|
|
for (ch = 3; ch >= 0; ch--) {
|
|
i = UBYTE(module[0x1c6 + ch]) + (UBYTE(module[0x1ca + ch]) << 8) - addr_to_offset;
|
|
i = UBYTE(module[i + pos * 2]);
|
|
if (i >= 0x40)
|
|
break;
|
|
i <<= 1;
|
|
i = UWORD(module, 0x46 + i);
|
|
pattern_offset[ch] = i == 0 ? 0 : i - addr_to_offset;
|
|
blank_rows_counter[ch] = 0;
|
|
}
|
|
if (ch >= 0)
|
|
break;
|
|
for (i = 0; i < song_len; i++)
|
|
if (seen[i] == SEEN_THIS_CALL)
|
|
seen[i] = SEEN_BEFORE;
|
|
for (pattern_rows = UBYTE(module[0x1ce]); --pattern_rows >= 0; ) {
|
|
for (ch = 3; ch >= 0; ch--) {
|
|
if (pattern_offset[ch] == 0 || --blank_rows_counter[ch] >= 0)
|
|
continue;
|
|
for (;;) {
|
|
i = UBYTE(module[pattern_offset[ch]++]);
|
|
if (i < 0x40 || i == 0xfe)
|
|
break;
|
|
if (i < 0x80)
|
|
continue;
|
|
if (i < 0xc0) {
|
|
blank_rows[ch] = i - 0x80;
|
|
continue;
|
|
}
|
|
if (i < 0xd0)
|
|
continue;
|
|
if (i < 0xe0) {
|
|
tempo = i - 0xcf;
|
|
continue;
|
|
}
|
|
pattern_rows = 0;
|
|
}
|
|
blank_rows_counter[ch] = blank_rows[ch];
|
|
}
|
|
player_calls += tempo;
|
|
}
|
|
pos++;
|
|
}
|
|
if (player_calls > 0)
|
|
set_song_duration(module_info, player_calls);
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_mpt, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, track0_addr);
|
|
V(int, pos);
|
|
V(int, song_len);
|
|
/* seen[i] == TRUE if the track position i has been processed */
|
|
NEW_ARRAY(abool, global_seen, 256);
|
|
if (module_len < 0x1d0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_MPT;
|
|
if (!load_native(ast, module_info, module, module_len, GET_PLAYER(mpt)))
|
|
return FALSE;
|
|
track0_addr = UWORD(module, 2) + 0x1ca;
|
|
if (UBYTE(module[0x1c6]) + (UBYTE(module[0x1ca]) << 8) != track0_addr)
|
|
return FALSE;
|
|
/* Calculate the length of the first track. Address of the second track minus
|
|
address of the first track equals the length of the first track in bytes.
|
|
Divide by two to get number of track positions. */
|
|
song_len = (UBYTE(module[0x1c7]) + (UBYTE(module[0x1cb]) << 8) - track0_addr) >> 1;
|
|
if (song_len > 0xfe)
|
|
return FALSE;
|
|
INIT_ARRAY(global_seen);
|
|
module_info _ songs = 0;
|
|
for (pos = 0; pos < song_len && module_info _ songs < ASAP_SONGS_MAX; pos++) {
|
|
if (!global_seen[pos]) {
|
|
module_info _ song_pos[module_info _ songs] = CAST(byte) pos;
|
|
parse_mpt_song(module_info, module, global_seen, song_len, pos);
|
|
}
|
|
}
|
|
return module_info _ songs > 0;
|
|
}
|
|
|
|
CONST_ARRAY(byte, rmt_volume_silent)
|
|
16, 8, 4, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1
|
|
END_CONST_ARRAY;
|
|
|
|
PRIVATE FUNC(int, rmt_instrument_frames, (
|
|
P(CONST BYTEARRAY, module), P(int, instrument),
|
|
P(int, volume), P(int, volume_frame), P(abool, extra_pokey)))
|
|
{
|
|
V(int, addr_to_offset) = UWORD(module, 2) - 6;
|
|
V(int, per_frame) = module[0xc];
|
|
V(int, player_call);
|
|
V(int, player_calls);
|
|
V(int, index);
|
|
V(int, index_end);
|
|
V(int, index_loop);
|
|
V(int, volume_slide_depth);
|
|
V(int, volume_min);
|
|
V(int, volume_slide);
|
|
V(abool, silent_loop);
|
|
instrument = UWORD(module, 0xe) - addr_to_offset + (instrument << 1);
|
|
if (module[instrument + 1] == 0)
|
|
return 0;
|
|
instrument = UWORD(module, instrument) - addr_to_offset;
|
|
player_calls = player_call = volume_frame * per_frame;
|
|
index = UBYTE(module[instrument]) + 1 + player_call * 3;
|
|
index_end = UBYTE(module[instrument + 2]) + 3;
|
|
index_loop = UBYTE(module[instrument + 3]);
|
|
if (index_loop >= index_end)
|
|
return 0; /* error */
|
|
volume_slide_depth = UBYTE(module[instrument + 6]);
|
|
volume_min = UBYTE(module[instrument + 7]);
|
|
if (index >= index_end)
|
|
index = (index - index_end) % (index_end - index_loop) + index_loop;
|
|
else {
|
|
do {
|
|
V(int, vol) = module[instrument + index];
|
|
if (extra_pokey)
|
|
vol >>= 4;
|
|
if ((vol & 0xf) >= rmt_volume_silent[volume])
|
|
player_calls = player_call + 1;
|
|
player_call++;
|
|
index += 3;
|
|
} while (index < index_end);
|
|
}
|
|
if (volume_slide_depth == 0)
|
|
return player_calls / per_frame;
|
|
volume_slide = 128;
|
|
silent_loop = FALSE;
|
|
for (;;) {
|
|
V(int, vol);
|
|
if (index >= index_end) {
|
|
if (silent_loop)
|
|
break;
|
|
silent_loop = TRUE;
|
|
index = index_loop;
|
|
}
|
|
vol = module[instrument + index];
|
|
if (extra_pokey)
|
|
vol >>= 4;
|
|
if ((vol & 0xf) >= rmt_volume_silent[volume]) {
|
|
player_calls = player_call + 1;
|
|
silent_loop = FALSE;
|
|
}
|
|
player_call++;
|
|
index += 3;
|
|
volume_slide -= volume_slide_depth;
|
|
if (volume_slide < 0) {
|
|
volume_slide += 256;
|
|
if (--volume <= volume_min)
|
|
break;
|
|
}
|
|
}
|
|
return player_calls / per_frame;
|
|
}
|
|
|
|
PRIVATE FUNC(void, parse_rmt_song, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module),
|
|
P(BOOLARRAY, global_seen), P(int, song_len), P(int, pos_shift), P(int, pos)))
|
|
{
|
|
V(int, ch);
|
|
V(int, addr_to_offset) = UWORD(module, 2) - 6;
|
|
V(int, tempo) = UBYTE(module[0xb]);
|
|
V(int, frames) = 0;
|
|
V(int, song_offset) = UWORD(module, 0x14) - addr_to_offset;
|
|
V(int, pattern_lo_offset) = UWORD(module, 0x10) - addr_to_offset;
|
|
V(int, pattern_hi_offset) = UWORD(module, 0x12) - addr_to_offset;
|
|
V(int, instrument_frames);
|
|
NEW_ARRAY(byte, seen, 256);
|
|
NEW_ARRAY(int, pattern_begin, 8);
|
|
NEW_ARRAY(int, pattern_offset, 8);
|
|
NEW_ARRAY(int, blank_rows, 8);
|
|
NEW_ARRAY(int, instrument_no, 8);
|
|
NEW_ARRAY(int, instrument_frame, 8);
|
|
NEW_ARRAY(int, volume_value, 8);
|
|
NEW_ARRAY(int, volume_frame, 8);
|
|
INIT_ARRAY(seen);
|
|
INIT_ARRAY(instrument_no);
|
|
INIT_ARRAY(instrument_frame);
|
|
INIT_ARRAY(volume_value);
|
|
INIT_ARRAY(volume_frame);
|
|
while (pos < song_len) {
|
|
V(int, i);
|
|
V(int, pattern_rows);
|
|
if (seen[pos] != 0) {
|
|
if (seen[pos] != SEEN_THIS_CALL)
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
break;
|
|
}
|
|
seen[pos] = SEEN_THIS_CALL;
|
|
global_seen[pos] = TRUE;
|
|
if (UBYTE(module[song_offset + (pos << pos_shift)]) == 0xfe) {
|
|
pos = UBYTE(module[song_offset + (pos << pos_shift) + 1]);
|
|
continue;
|
|
}
|
|
for (ch = 0; ch < 1 << pos_shift; ch++) {
|
|
i = UBYTE(module[song_offset + (pos << pos_shift) + ch]);
|
|
if (i == 0xff)
|
|
blank_rows[ch] = 256;
|
|
else {
|
|
pattern_offset[ch] = pattern_begin[ch] = UBYTE(module[pattern_lo_offset + i])
|
|
+ (UBYTE(module[pattern_hi_offset + i]) << 8) - addr_to_offset;
|
|
blank_rows[ch] = 0;
|
|
}
|
|
}
|
|
for (i = 0; i < song_len; i++)
|
|
if (seen[i] == SEEN_THIS_CALL)
|
|
seen[i] = SEEN_BEFORE;
|
|
for (pattern_rows = UBYTE(module[0xa]); --pattern_rows >= 0; ) {
|
|
for (ch = 0; ch < 1 << pos_shift; ch++) {
|
|
if (--blank_rows[ch] > 0)
|
|
continue;
|
|
for (;;) {
|
|
i = UBYTE(module[pattern_offset[ch]++]);
|
|
if ((i & 0x3f) < 62) {
|
|
i += UBYTE(module[pattern_offset[ch]++]) << 8;
|
|
if ((i & 0x3f) != 61) {
|
|
instrument_no[ch] = i >> 10;
|
|
instrument_frame[ch] = frames;
|
|
}
|
|
volume_value[ch] = (i >> 6) & 0xf;
|
|
volume_frame[ch] = frames;
|
|
break;
|
|
}
|
|
if (i == 62) {
|
|
blank_rows[ch] = UBYTE(module[pattern_offset[ch]++]);
|
|
break;
|
|
}
|
|
if ((i & 0x3f) == 62) {
|
|
blank_rows[ch] = i >> 6;
|
|
break;
|
|
}
|
|
if ((i & 0xbf) == 63) {
|
|
tempo = UBYTE(module[pattern_offset[ch]++]);
|
|
continue;
|
|
}
|
|
if (i == 0xbf) {
|
|
pattern_offset[ch] = pattern_begin[ch] + UBYTE(module[pattern_offset[ch]]);
|
|
continue;
|
|
}
|
|
/* assert(i == 0xff); */
|
|
pattern_rows = -1;
|
|
break;
|
|
}
|
|
if (pattern_rows < 0)
|
|
break;
|
|
}
|
|
if (pattern_rows >= 0)
|
|
frames += tempo;
|
|
}
|
|
pos++;
|
|
}
|
|
instrument_frames = 0;
|
|
for (ch = 0; ch < 1 << pos_shift; ch++) {
|
|
V(int, frame) = instrument_frame[ch];
|
|
frame += rmt_instrument_frames(module, instrument_no[ch], volume_value[ch], volume_frame[ch] - frame, ch >= 4);
|
|
if (instrument_frames < frame)
|
|
instrument_frames = frame;
|
|
}
|
|
if (frames > instrument_frames) {
|
|
if (frames - instrument_frames > 100)
|
|
module_info _ loops[module_info _ songs] = FALSE;
|
|
frames = instrument_frames;
|
|
}
|
|
if (frames > 0)
|
|
set_song_duration(module_info, frames);
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_rmt, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, per_frame);
|
|
V(int, pos_shift);
|
|
V(int, song_len);
|
|
V(int, pos);
|
|
NEW_ARRAY(abool, global_seen, 256);
|
|
if (module_len < 0x30 || module[6] != CHARCODE('R') || module[7] != CHARCODE('M')
|
|
|| module[8] != CHARCODE('T') || module[0xd] != 1)
|
|
return FALSE;
|
|
switch (CAST(char) module[9]) {
|
|
case CHARCODE('4'):
|
|
pos_shift = 2;
|
|
break;
|
|
case CHARCODE('8'):
|
|
module_info _ channels = 2;
|
|
pos_shift = 3;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
per_frame = module[0xc];
|
|
if (per_frame < 1 || per_frame > 4)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_RMT;
|
|
if (!load_native(ast, module_info, module, module_len,
|
|
module_info _ channels == 2 ? GET_PLAYER(rmt8) : GET_PLAYER(rmt4)))
|
|
return FALSE;
|
|
song_len = UWORD(module, 4) + 1 - UWORD(module, 0x14);
|
|
if (pos_shift == 3 && (song_len & 4) != 0
|
|
&& UBYTE(module[6 + UWORD(module, 4) - UWORD(module, 2) - 3]) == 0xfe)
|
|
song_len += 4;
|
|
song_len >>= pos_shift;
|
|
if (song_len >= 0x100)
|
|
return FALSE;
|
|
INIT_ARRAY(global_seen);
|
|
module_info _ songs = 0;
|
|
for (pos = 0; pos < song_len && module_info _ songs < ASAP_SONGS_MAX; pos++) {
|
|
if (!global_seen[pos]) {
|
|
module_info _ song_pos[module_info _ songs] = CAST(byte) pos;
|
|
parse_rmt_song(module_info, module, global_seen, song_len, pos_shift, pos);
|
|
}
|
|
}
|
|
/* must set fastplay after song durations calculations, so they assume 312 */
|
|
module_info _ fastplay = perframe2fastplay[per_frame - 1];
|
|
module_info _ player = 0x600;
|
|
return module_info _ songs > 0;
|
|
}
|
|
|
|
PRIVATE FUNC(void, parse_tmc_song, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module), P(int, pos)))
|
|
{
|
|
V(int, addr_to_offset) = UWORD(module, 2) - 6;
|
|
V(int, tempo) = UBYTE(module[0x24]) + 1;
|
|
V(int, frames) = 0;
|
|
NEW_ARRAY(int, pattern_offset, 8);
|
|
NEW_ARRAY(int, blank_rows, 8);
|
|
while (UBYTE(module[0x1a6 + 15 + pos]) < 0x80) {
|
|
V(int, ch);
|
|
V(int, pattern_rows);
|
|
for (ch = 7; ch >= 0; ch--) {
|
|
V(int, pat) = UBYTE(module[0x1a6 + 15 + pos - 2 * ch]);
|
|
pattern_offset[ch] = UBYTE(module[0xa6 + pat]) + (UBYTE(module[0x126 + pat]) << 8) - addr_to_offset;
|
|
blank_rows[ch] = 0;
|
|
}
|
|
for (pattern_rows = 64; --pattern_rows >= 0; ) {
|
|
for (ch = 7; ch >= 0; ch--) {
|
|
if (--blank_rows[ch] >= 0)
|
|
continue;
|
|
for (;;) {
|
|
V(int, i) = UBYTE(module[pattern_offset[ch]++]);
|
|
if (i < 0x40) {
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i == 0x40) {
|
|
i = UBYTE(module[pattern_offset[ch]++]);
|
|
if ((i & 0x7f) == 0)
|
|
pattern_rows = 0;
|
|
else
|
|
tempo = (i & 0x7f) + 1;
|
|
if (i >= 0x80)
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i < 0x80) {
|
|
i = module[pattern_offset[ch]++] & 0x7f;
|
|
if (i == 0)
|
|
pattern_rows = 0;
|
|
else
|
|
tempo = i + 1;
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i < 0xc0)
|
|
continue;
|
|
blank_rows[ch] = i - 0xbf;
|
|
break;
|
|
}
|
|
}
|
|
frames += tempo;
|
|
}
|
|
pos += 16;
|
|
}
|
|
if (UBYTE(module[0x1a6 + 14 + pos]) < 0x80)
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
set_song_duration(module_info, frames);
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_tmc, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, i);
|
|
V(int, last_pos);
|
|
if (module_len < 0x1d0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_TMC;
|
|
if (!load_native(ast, module_info, module, module_len, GET_PLAYER(tmc)))
|
|
return FALSE;
|
|
module_info _ channels = 2;
|
|
i = 0;
|
|
/* find first instrument */
|
|
while (module[0x66 + i] == 0) {
|
|
if (++i >= 64)
|
|
return FALSE; /* no instrument */
|
|
}
|
|
last_pos = (UBYTE(module[0x66 + i]) << 8) + UBYTE(module[0x26 + i])
|
|
- UWORD(module, 2) - 0x1b0;
|
|
if (0x1b5 + last_pos >= module_len)
|
|
return FALSE;
|
|
/* skip trailing jumps */
|
|
do {
|
|
if (last_pos <= 0)
|
|
return FALSE; /* no pattern to play */
|
|
last_pos -= 16;
|
|
} while (UBYTE(module[0x1b5 + last_pos]) >= 0x80);
|
|
module_info _ songs = 0;
|
|
parse_tmc_song(module_info, module, 0);
|
|
for (i = 0; i < last_pos && module_info _ songs < ASAP_SONGS_MAX; i += 16)
|
|
if (UBYTE(module[0x1b5 + i]) >= 0x80)
|
|
parse_tmc_song(module_info, module, i + 16);
|
|
/* must set fastplay after song durations calculations, so they assume 312 */
|
|
i = module[0x25];
|
|
if (i < 1 || i > 4)
|
|
return FALSE;
|
|
if (ast != NULL)
|
|
ast _ tmc_per_frame = module[0x25];
|
|
module_info _ fastplay = perframe2fastplay[i - 1];
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE FUNC(void, parse_tm2_song, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module), P(int, pos)))
|
|
{
|
|
V(int, addr_to_offset) = UWORD(module, 2) - 6;
|
|
V(int, tempo) = UBYTE(module[0x24]) + 1;
|
|
V(int, player_calls) = 0;
|
|
NEW_ARRAY(int, pattern_offset, 8);
|
|
NEW_ARRAY(int, blank_rows, 8);
|
|
for (;;) {
|
|
V(int, ch);
|
|
V(int, pattern_rows) = UBYTE(module[0x386 + 16 + pos]);
|
|
if (pattern_rows == 0)
|
|
break;
|
|
if (pattern_rows >= 0x80) {
|
|
module_info _ loops[module_info _ songs] = TRUE;
|
|
break;
|
|
}
|
|
for (ch = 7; ch >= 0; ch--) {
|
|
V(int, pat) = UBYTE(module[0x386 + 15 + pos - 2 * ch]);
|
|
pattern_offset[ch] = UBYTE(module[0x106 + pat]) + (UBYTE(module[0x206 + pat]) << 8) - addr_to_offset;
|
|
blank_rows[ch] = 0;
|
|
}
|
|
while (--pattern_rows >= 0) {
|
|
for (ch = 7; ch >= 0; ch--) {
|
|
if (--blank_rows[ch] >= 0)
|
|
continue;
|
|
for (;;) {
|
|
V(int, i) = UBYTE(module[pattern_offset[ch]++]);
|
|
if (i == 0) {
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i < 0x40) {
|
|
if (UBYTE(module[pattern_offset[ch]++]) >= 0x80)
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i < 0x80) {
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i == 0x80) {
|
|
blank_rows[ch] = UBYTE(module[pattern_offset[ch]++]);
|
|
break;
|
|
}
|
|
if (i < 0xc0)
|
|
break;
|
|
if (i < 0xd0) {
|
|
tempo = i - 0xbf;
|
|
continue;
|
|
}
|
|
if (i < 0xe0) {
|
|
pattern_offset[ch]++;
|
|
break;
|
|
}
|
|
if (i < 0xf0) {
|
|
pattern_offset[ch] += 2;
|
|
break;
|
|
}
|
|
if (i < 0xff) {
|
|
blank_rows[ch] = i - 0xf0;
|
|
break;
|
|
}
|
|
blank_rows[ch] = 64;
|
|
break;
|
|
}
|
|
}
|
|
player_calls += tempo;
|
|
}
|
|
pos += 17;
|
|
}
|
|
set_song_duration(module_info, player_calls);
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_tm2, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, i);
|
|
V(int, last_pos);
|
|
V(int, c);
|
|
if (module_len < 0x3a4)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_TM2;
|
|
if (!load_native(ast, module_info, module, module_len, GET_PLAYER(tm2)))
|
|
return FALSE;
|
|
i = module[0x25];
|
|
if (i < 1 || i > 4)
|
|
return FALSE;
|
|
module_info _ fastplay = perframe2fastplay[i - 1];
|
|
module_info _ player = 0x500;
|
|
if (module[0x1f] != 0)
|
|
module_info _ channels = 2;
|
|
last_pos = 0xffff;
|
|
for (i = 0; i < 0x80; i++) {
|
|
V(int, instr_addr) = UBYTE(module[0x86 + i]) + (UBYTE(module[0x306 + i]) << 8);
|
|
if (instr_addr != 0 && instr_addr < last_pos)
|
|
last_pos = instr_addr;
|
|
}
|
|
for (i = 0; i < 0x100; i++) {
|
|
V(int, pattern_addr) = UBYTE(module[0x106 + i]) + (UBYTE(module[0x206 + i]) << 8);
|
|
if (pattern_addr != 0 && pattern_addr < last_pos)
|
|
last_pos = pattern_addr;
|
|
}
|
|
last_pos -= UWORD(module, 2) + 0x380;
|
|
if (0x386 + last_pos >= module_len)
|
|
return FALSE;
|
|
/* skip trailing stop/jump commands */
|
|
do {
|
|
if (last_pos <= 0)
|
|
return FALSE;
|
|
last_pos -= 17;
|
|
c = UBYTE(module[0x386 + 16 + last_pos]);
|
|
} while (c == 0 || c >= 0x80);
|
|
module_info _ songs = 0;
|
|
parse_tm2_song(module_info, module, 0);
|
|
for (i = 0; i < last_pos && module_info _ songs < ASAP_SONGS_MAX; i += 17) {
|
|
c = UBYTE(module[0x386 + 16 + i]);
|
|
if (c == 0 || c >= 0x80)
|
|
parse_tm2_song(module_info, module, i + 17);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#endif /* ASAP_ONLY_SAP */
|
|
|
|
PRIVATE FUNC(abool, has_string_at, (P(CONST BYTEARRAY, module), P(int, module_index), P(STRING, s)))
|
|
{
|
|
V(int, i);
|
|
V(int, n) = strlen(s);
|
|
for (i = 0; i < n; i++)
|
|
if (module[module_index + i] != CHARCODEAT(s, i))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE FUNC(STRING, parse_text, (P(OUT_STRING, dest), P(CONST BYTEARRAY, module), P(int, module_index)))
|
|
{
|
|
V(int, i);
|
|
if (module[module_index] != CHARCODE('"'))
|
|
return NULL;
|
|
if (has_string_at(module, module_index + 1, "<?>\""))
|
|
return dest;
|
|
for (i = 0; ; i++) {
|
|
V(int, c) = module[module_index + 1 + i];
|
|
if (c == CHARCODE('"'))
|
|
break;
|
|
if (c < 32 || c >= 127)
|
|
return NULL;
|
|
}
|
|
BYTES_TO_STRING(dest, module, module_index + 1, i);
|
|
return dest;
|
|
}
|
|
|
|
PRIVATE FUNC(int, parse_dec, (P(CONST BYTEARRAY, module), P(int, module_index), P(int, maxval)))
|
|
{
|
|
V(int, r);
|
|
if (module[module_index] == 0xd)
|
|
return -1;
|
|
for (r = 0;;) {
|
|
V(int, c) = module[module_index++];
|
|
if (c == 0xd)
|
|
break;
|
|
if (c < CHARCODE('0') || c > CHARCODE('9'))
|
|
return -1;
|
|
r = 10 * r + c - 48;
|
|
if (r > maxval)
|
|
return -1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
PRIVATE FUNC(int, parse_hex, (P(CONST BYTEARRAY, module), P(int, module_index)))
|
|
{
|
|
V(int, r);
|
|
if (module[module_index] == 0xd)
|
|
return -1;
|
|
for (r = 0;;) {
|
|
V(int, c) = module[module_index++];
|
|
if (c == 0xd)
|
|
break;
|
|
if (r > 0xfff)
|
|
return -1;
|
|
r <<= 4;
|
|
if (c >= CHARCODE('0') && c <= CHARCODE('9'))
|
|
r += c - CHARCODE('0');
|
|
else if (c >= CHARCODE('A') && c <= CHARCODE('F'))
|
|
r += c - CHARCODE('A') + 10;
|
|
else if (c >= CHARCODE('a') && c <= CHARCODE('f'))
|
|
r += c - CHARCODE('a') + 10;
|
|
else
|
|
return -1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
FUNC(int, ASAP_ParseDuration, (P(STRING, s)))
|
|
{
|
|
V(int, i) = 0;
|
|
V(int, r);
|
|
V(int, d);
|
|
V(int, n) = strlen(s);
|
|
#define PARSE_DIGIT(maxdig, retifnot) \
|
|
if (i >= n) \
|
|
return retifnot; \
|
|
d = CHARCODEAT(s, i) - 48; \
|
|
if (d < 0 || d > maxdig) \
|
|
return -1; \
|
|
i++;
|
|
|
|
PARSE_DIGIT(9, -1);
|
|
r = d;
|
|
if (i < n) {
|
|
d = CHARCODEAT(s, i) - 48;
|
|
if (d >= 0 && d <= 9) {
|
|
i++;
|
|
r = 10 * r + d;
|
|
}
|
|
if (i < n && CHARAT(s, i) == ':') {
|
|
i++;
|
|
PARSE_DIGIT(5, -1);
|
|
r = (6 * r + d) * 10;
|
|
PARSE_DIGIT(9, -1);
|
|
r += d;
|
|
}
|
|
}
|
|
r *= 1000;
|
|
if (i >= n)
|
|
return r;
|
|
if (CHARAT(s, i) != '.')
|
|
return -1;
|
|
i++;
|
|
PARSE_DIGIT(9, -1);
|
|
r += 100 * d;
|
|
PARSE_DIGIT(9, r);
|
|
r += 10 * d;
|
|
PARSE_DIGIT(9, r);
|
|
r += d;
|
|
return r;
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_sap_header, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, module_index);
|
|
V(int, type) = 0;
|
|
V(int, duration_index) = 0;
|
|
if (!has_string_at(module, 0, "SAP\r\n"))
|
|
return FALSE;
|
|
module_info _ fastplay = -1;
|
|
module_index = 5;
|
|
while (UBYTE(module[module_index]) != 0xff) {
|
|
if (module_index + 8 >= module_len)
|
|
return FALSE;
|
|
#define TAG_IS(s) has_string_at(module, module_index, s)
|
|
#ifdef C
|
|
#define SET_TEXT(v, i) if (parse_text(v, module, module_index + i) == NULL) return FALSE
|
|
#else
|
|
#define SET_TEXT(v, i) v = parse_text(v, module, module_index + i); if (v == NULL) return FALSE
|
|
#endif
|
|
#define SET_DEC(v, i, min, max) v = parse_dec(module, module_index + i, max); if (v < min) return FALSE
|
|
#define SET_HEX(v, i) v = parse_hex(module, module_index + i)
|
|
if (TAG_IS("AUTHOR ")) {
|
|
SET_TEXT(module_info _ author, 7);
|
|
}
|
|
else if (TAG_IS("NAME ")) {
|
|
SET_TEXT(module_info _ name, 5);
|
|
}
|
|
else if (TAG_IS("DATE ")) {
|
|
SET_TEXT(module_info _ date, 5);
|
|
}
|
|
else if (TAG_IS("SONGS ")) {
|
|
SET_DEC(module_info _ songs, 6, 1, ASAP_SONGS_MAX);
|
|
}
|
|
else if (TAG_IS("DEFSONG ")) {
|
|
SET_DEC(module_info _ default_song, 8, 0, ASAP_SONGS_MAX - 1);
|
|
}
|
|
else if (TAG_IS("STEREO\r"))
|
|
module_info _ channels = 2;
|
|
else if (TAG_IS("NTSC\r"))
|
|
module_info _ ntsc = TRUE;
|
|
else if (TAG_IS("TIME ")) {
|
|
V(int, i);
|
|
#ifdef C
|
|
char s[ASAP_DURATION_CHARS];
|
|
#else
|
|
V(STRING, s);
|
|
#endif
|
|
module_index += 5;
|
|
for (i = 0; module[module_index + i] != 0xd; i++) { }
|
|
if (i > 5 && has_string_at(module, module_index + i - 5, " LOOP")) {
|
|
module_info _ loops[duration_index] = TRUE;
|
|
i -= 5;
|
|
}
|
|
#ifdef C
|
|
if (i >= ASAP_DURATION_CHARS)
|
|
return FALSE;
|
|
#endif
|
|
BYTES_TO_STRING(s, module, module_index, i);
|
|
i = ASAP_ParseDuration(s);
|
|
if (i < 0 || duration_index >= ASAP_SONGS_MAX)
|
|
return FALSE;
|
|
module_info _ durations[duration_index++] = i;
|
|
}
|
|
else if (TAG_IS("TYPE "))
|
|
type = module[module_index + 5];
|
|
else if (TAG_IS("FASTPLAY ")) {
|
|
SET_DEC(module_info _ fastplay, 9, 1, 312);
|
|
}
|
|
else if (TAG_IS("MUSIC ")) {
|
|
SET_HEX(module_info _ music, 6);
|
|
}
|
|
else if (TAG_IS("INIT ")) {
|
|
SET_HEX(module_info _ init, 5);
|
|
}
|
|
else if (TAG_IS("PLAYER ")) {
|
|
SET_HEX(module_info _ player, 7);
|
|
}
|
|
else if (TAG_IS("COVOX ")) {
|
|
SET_HEX(module_info _ covox_addr, 6);
|
|
if (module_info _ covox_addr != 0xd600)
|
|
return FALSE;
|
|
module_info _ channels = 2;
|
|
}
|
|
|
|
while (module[module_index++] != 0x0d) {
|
|
if (module_index >= module_len)
|
|
return FALSE;
|
|
}
|
|
if (module[module_index++] != 0x0a)
|
|
return FALSE;
|
|
}
|
|
if (module_info _ default_song >= module_info _ songs)
|
|
return FALSE;
|
|
switch (type) {
|
|
case CHARCODE('B'):
|
|
if (module_info _ player < 0 || module_info _ init < 0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_SAP_B;
|
|
break;
|
|
case CHARCODE('C'):
|
|
if (module_info _ player < 0 || module_info _ music < 0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_SAP_C;
|
|
break;
|
|
case CHARCODE('D'):
|
|
if (module_info _ init < 0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_SAP_D;
|
|
break;
|
|
case CHARCODE('S'):
|
|
if (module_info _ init < 0)
|
|
return FALSE;
|
|
module_info _ type = ASAP_TYPE_SAP_S;
|
|
module_info _ fastplay = 78;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
if (module_info _ fastplay < 0)
|
|
module_info _ fastplay = module_info _ ntsc ? 262 : 312;
|
|
else if (module_info _ ntsc && module_info _ fastplay > 262)
|
|
return FALSE;
|
|
if (UBYTE(module[module_index + 1]) != 0xff)
|
|
return FALSE;
|
|
module_info _ header_len = module_index;
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_sap, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, module_index);
|
|
if (!parse_sap_header(module_info, module, module_len))
|
|
return FALSE;
|
|
if (ast == NULL)
|
|
return TRUE;
|
|
module_index = module_info _ header_len + 2;
|
|
while (module_index + 5 <= module_len) {
|
|
V(int, start_addr) = UWORD(module, module_index);
|
|
V(int, block_len) = UWORD(module, module_index + 2) + 1 - start_addr;
|
|
if (block_len <= 0 || module_index + block_len > module_len)
|
|
return FALSE;
|
|
module_index += 4;
|
|
COPY_ARRAY(ast _ memory, start_addr, module, module_index, block_len);
|
|
module_index += block_len;
|
|
if (module_index == module_len)
|
|
return TRUE;
|
|
if (module_index + 7 <= module_len
|
|
&& UBYTE(module[module_index]) == 0xff && UBYTE(module[module_index + 1]) == 0xff)
|
|
module_index += 2;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#define ASAP_EXT(c1, c2, c3) ((CHARCODE(c1) + (CHARCODE(c2) << 8) + (CHARCODE(c3) << 16)) | 0x202020)
|
|
|
|
PRIVATE FUNC(int, get_packed_ext, (P(STRING, filename)))
|
|
{
|
|
V(int, i) = strlen(filename);
|
|
V(int, ext) = 0;
|
|
while (--i > 0) {
|
|
V(char, c) = CHARAT(filename, i);
|
|
if (c <= ' ' || c > 'z')
|
|
return 0;
|
|
if (c == '.')
|
|
return ext | 0x202020;
|
|
ext = (ext << 8) + CHARCODE(c);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PRIVATE FUNC(abool, is_our_ext, (P(int, ext)))
|
|
{
|
|
switch (ext) {
|
|
case ASAP_EXT('S', 'A', 'P'):
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_EXT('C', 'M', 'C'):
|
|
case ASAP_EXT('C', 'M', '3'):
|
|
case ASAP_EXT('C', 'M', 'R'):
|
|
case ASAP_EXT('C', 'M', 'S'):
|
|
case ASAP_EXT('D', 'M', 'C'):
|
|
case ASAP_EXT('D', 'L', 'T'):
|
|
case ASAP_EXT('M', 'P', 'T'):
|
|
case ASAP_EXT('M', 'P', 'D'):
|
|
case ASAP_EXT('R', 'M', 'T'):
|
|
case ASAP_EXT('T', 'M', 'C'):
|
|
case ASAP_EXT('T', 'M', '8'):
|
|
case ASAP_EXT('T', 'M', '2'):
|
|
#endif
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
FUNC(abool, ASAP_IsOurFile, (P(STRING, filename)))
|
|
{
|
|
V(int, ext) = get_packed_ext(filename);
|
|
return is_our_ext(ext);
|
|
}
|
|
|
|
FUNC(abool, ASAP_IsOurExt, (P(STRING, ext)))
|
|
{
|
|
return strlen(ext) == 3
|
|
&& is_our_ext(ASAP_EXT(CHARAT(ext, 0), CHARAT(ext, 1), CHARAT(ext, 2)));
|
|
}
|
|
|
|
PRIVATE FUNC(abool, parse_file, (
|
|
P(ASAP_State PTR, ast), P(ASAP_ModuleInfo PTR, module_info),
|
|
P(STRING, filename), P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
V(int, i);
|
|
V(int, len) = strlen(filename);
|
|
V(int, basename) = 0;
|
|
V(int, ext) = -1;
|
|
for (i = 0; i < len; i++) {
|
|
V(char, c) = CHARAT(filename, i);
|
|
if (c == '/' || c == '\\') {
|
|
basename = i + 1;
|
|
ext = -1;
|
|
}
|
|
else if (c == '.')
|
|
ext = i;
|
|
}
|
|
if (ext < 0)
|
|
return FALSE;
|
|
EMPTY_STRING(module_info _ author);
|
|
SUBSTRING(module_info _ name, filename, basename, ext - basename);
|
|
EMPTY_STRING(module_info _ date);
|
|
module_info _ channels = 1;
|
|
module_info _ songs = 1;
|
|
module_info _ default_song = 0;
|
|
for (i = 0; i < ASAP_SONGS_MAX; i++) {
|
|
module_info _ durations[i] = -1;
|
|
module_info _ loops[i] = FALSE;
|
|
}
|
|
module_info _ ntsc = FALSE;
|
|
module_info _ fastplay = 312;
|
|
module_info _ music = -1;
|
|
module_info _ init = -1;
|
|
module_info _ player = -1;
|
|
module_info _ covox_addr = -1;
|
|
switch (get_packed_ext(filename)) {
|
|
case ASAP_EXT('S', 'A', 'P'):
|
|
return parse_sap(ast, module_info, module, module_len);
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_EXT('C', 'M', 'C'):
|
|
return parse_cmc(ast, module_info, module, module_len, ASAP_TYPE_CMC, GET_PLAYER(cmc));
|
|
case ASAP_EXT('C', 'M', '3'):
|
|
return parse_cmc(ast, module_info, module, module_len, ASAP_TYPE_CM3, GET_PLAYER(cm3));
|
|
case ASAP_EXT('C', 'M', 'R'):
|
|
return parse_cmc(ast, module_info, module, module_len, ASAP_TYPE_CMR, GET_PLAYER(cmc));
|
|
case ASAP_EXT('C', 'M', 'S'):
|
|
module_info _ channels = 2;
|
|
return parse_cmc(ast, module_info, module, module_len, ASAP_TYPE_CMS, GET_PLAYER(cms));
|
|
case ASAP_EXT('D', 'M', 'C'):
|
|
module_info _ fastplay = 156;
|
|
return parse_cmc(ast, module_info, module, module_len, ASAP_TYPE_CMC, GET_PLAYER(cmc));
|
|
case ASAP_EXT('D', 'L', 'T'):
|
|
return parse_dlt(ast, module_info, module, module_len);
|
|
case ASAP_EXT('M', 'P', 'T'):
|
|
return parse_mpt(ast, module_info, module, module_len);
|
|
case ASAP_EXT('M', 'P', 'D'):
|
|
module_info _ fastplay = 156;
|
|
return parse_mpt(ast, module_info, module, module_len);
|
|
case ASAP_EXT('R', 'M', 'T'):
|
|
return parse_rmt(ast, module_info, module, module_len);
|
|
case ASAP_EXT('T', 'M', 'C'):
|
|
case ASAP_EXT('T', 'M', '8'):
|
|
return parse_tmc(ast, module_info, module, module_len);
|
|
case ASAP_EXT('T', 'M', '2'):
|
|
return parse_tm2(ast, module_info, module, module_len);
|
|
#endif
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
FUNC(abool, ASAP_GetModuleInfo, (
|
|
P(ASAP_ModuleInfo PTR, module_info), P(STRING, filename),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
return parse_file(NULL, module_info, filename, module, module_len);
|
|
}
|
|
|
|
#ifndef ASAP_ONLY_INFO
|
|
|
|
FUNC(abool, ASAP_Load, (
|
|
P(ASAP_State PTR, ast), P(STRING, filename),
|
|
P(CONST BYTEARRAY, module), P(int, module_len)))
|
|
{
|
|
/* Set up ast */
|
|
ast _ memory = s_memory;
|
|
ast _ module_info = &s_module_info;
|
|
ast _ silence_cycles = 0;
|
|
return parse_file(ast, ast _ module_info, filename, module, module_len);
|
|
}
|
|
|
|
FUNC(void, ASAP_DetectSilence, (P(ASAP_State PTR, ast), P(int, seconds)))
|
|
{
|
|
ast _ silence_cycles = seconds * ASAP_MAIN_CLOCK(ast);
|
|
}
|
|
|
|
PRIVATE FUNC(void, call_6502, (P(ASAP_State PTR, ast), P(int, addr), P(int, max_scanlines)))
|
|
{
|
|
ast _ cpu_pc = addr;
|
|
/* put a CIM at 0xd20a and a return address on stack */
|
|
dPutByte(0xd20a, 0xd2);
|
|
dPutByte(0x01fe, 0x09);
|
|
dPutByte(0x01ff, 0xd2);
|
|
ast _ cpu_s = 0xfd;
|
|
Cpu_RunScanlines(ast, max_scanlines);
|
|
}
|
|
|
|
/* 50 Atari frames for the initialization routine - some SAPs are self-extracting. */
|
|
#define SCANLINES_FOR_INIT (50 * 312)
|
|
|
|
PRIVATE FUNC(void, call_6502_init, (P(ASAP_State PTR, ast), P(int, addr), P(int, a), P(int, x), P(int, y)))
|
|
{
|
|
ast _ cpu_a = a & 0xff;
|
|
ast _ cpu_x = x & 0xff;
|
|
ast _ cpu_y = y & 0xff;
|
|
call_6502(ast, addr, SCANLINES_FOR_INIT);
|
|
}
|
|
|
|
FUNC(void, ASAP_PlaySong, (P(ASAP_State PTR, ast), P(int, song), P(int, duration)))
|
|
{
|
|
ast _ current_song = song;
|
|
ast _ current_duration = duration;
|
|
ast _ blocks_played = 0;
|
|
ast _ silence_cycles_counter = ast _ silence_cycles;
|
|
ast _ extra_pokey_mask = ast _ module_info->channels > 1 ? 0x10 : 0;
|
|
ast _ consol = 8;
|
|
ast _ covox[0] = CAST(byte) 0x80;
|
|
ast _ covox[1] = CAST(byte) 0x80;
|
|
ast _ covox[2] = CAST(byte) 0x80;
|
|
ast _ covox[3] = CAST(byte) 0x80;
|
|
PokeySound_Initialize(ast);
|
|
ast _ cycle = 0;
|
|
ast _ cpu_nz = 0;
|
|
ast _ cpu_c = 0;
|
|
ast _ cpu_vdi = 0;
|
|
ast _ scanline_number = 0;
|
|
ast _ next_scanline_cycle = 0;
|
|
ast _ timer1_cycle = NEVER;
|
|
ast _ timer2_cycle = NEVER;
|
|
ast _ timer4_cycle = NEVER;
|
|
ast _ irqst = 0xff;
|
|
switch (ast _ module_info->type) {
|
|
case ASAP_TYPE_SAP_B:
|
|
call_6502_init(ast, ast _ module_info->init, song, 0, 0);
|
|
break;
|
|
case ASAP_TYPE_SAP_C:
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_TYPE_CMC:
|
|
case ASAP_TYPE_CM3:
|
|
case ASAP_TYPE_CMR:
|
|
case ASAP_TYPE_CMS:
|
|
#endif
|
|
call_6502_init(ast, ast _ module_info->player + 3, 0x70, ast _ module_info->music, ast _ module_info->music >> 8);
|
|
call_6502_init(ast, ast _ module_info->player + 3, 0x00, song, 0);
|
|
break;
|
|
case ASAP_TYPE_SAP_D:
|
|
case ASAP_TYPE_SAP_S:
|
|
ast _ cpu_a = song;
|
|
ast _ cpu_x = 0x00;
|
|
ast _ cpu_y = 0x00;
|
|
ast _ cpu_s = 0xff;
|
|
ast _ cpu_pc = ast _ module_info->init;
|
|
break;
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_TYPE_DLT:
|
|
call_6502_init(ast, ast _ module_info->player + 0x100, 0x00, 0x00, ast _ module_info->song_pos[song]);
|
|
break;
|
|
case ASAP_TYPE_MPT:
|
|
call_6502_init(ast, ast _ module_info->player, 0x00, ast _ module_info->music >> 8, ast _ module_info->music);
|
|
call_6502_init(ast, ast _ module_info->player, 0x02, ast _ module_info->song_pos[song], 0);
|
|
break;
|
|
case ASAP_TYPE_RMT:
|
|
call_6502_init(ast, ast _ module_info->player, ast _ module_info->song_pos[song], ast _ module_info->music, ast _ module_info->music >> 8);
|
|
break;
|
|
case ASAP_TYPE_TMC:
|
|
case ASAP_TYPE_TM2:
|
|
call_6502_init(ast, ast _ module_info->player, 0x70, ast _ module_info->music >> 8, ast _ module_info->music);
|
|
call_6502_init(ast, ast _ module_info->player, 0x00, song, 0);
|
|
ast _ tmc_per_frame_counter = 1;
|
|
break;
|
|
#endif
|
|
}
|
|
ASAP_MutePokeyChannels(ast, 0);
|
|
}
|
|
|
|
FUNC(void, ASAP_MutePokeyChannels, (P(ASAP_State PTR, ast), P(int, mask)))
|
|
{
|
|
PokeySound_Mute(ast, ADDRESSOF ast _ base_pokey, mask);
|
|
PokeySound_Mute(ast, ADDRESSOF ast _ extra_pokey, mask >> 4);
|
|
}
|
|
|
|
FUNC(abool, call_6502_player, (P(ASAP_State PTR, ast)))
|
|
{
|
|
V(int, player) = ast _ module_info->player;
|
|
PokeySound_StartFrame(ast);
|
|
switch (ast _ module_info->type) {
|
|
case ASAP_TYPE_SAP_B:
|
|
call_6502(ast, player, ast _ module_info->fastplay);
|
|
break;
|
|
case ASAP_TYPE_SAP_C:
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_TYPE_CMC:
|
|
case ASAP_TYPE_CM3:
|
|
case ASAP_TYPE_CMR:
|
|
case ASAP_TYPE_CMS:
|
|
#endif
|
|
call_6502(ast, player + 6, ast _ module_info->fastplay);
|
|
break;
|
|
case ASAP_TYPE_SAP_D:
|
|
if (player >= 0) {
|
|
V(int, s)= ast _ cpu_s;
|
|
#define PUSH_ON_6502_STACK(x) dPutByte(0x100 + s, x); s = (s - 1) & 0xff
|
|
#define RETURN_FROM_PLAYER_ADDR 0xd200
|
|
/* save 6502 state on 6502 stack */
|
|
PUSH_ON_6502_STACK(ast _ cpu_pc >> 8);
|
|
PUSH_ON_6502_STACK(ast _ cpu_pc & 0xff);
|
|
PUSH_ON_6502_STACK(((ast _ cpu_nz | (ast _ cpu_nz >> 1)) & 0x80) + ast _ cpu_vdi + \
|
|
((ast _ cpu_nz & 0xff) == 0 ? Z_FLAG : 0) + ast _ cpu_c + 0x20);
|
|
PUSH_ON_6502_STACK(ast _ cpu_a);
|
|
PUSH_ON_6502_STACK(ast _ cpu_x);
|
|
PUSH_ON_6502_STACK(ast _ cpu_y);
|
|
/* RTS will jump to 6502 code that restores the state */
|
|
PUSH_ON_6502_STACK((RETURN_FROM_PLAYER_ADDR - 1) >> 8);
|
|
PUSH_ON_6502_STACK((RETURN_FROM_PLAYER_ADDR - 1) & 0xff);
|
|
ast _ cpu_s = s;
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR, 0x68); /* PLA */
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR + 1, 0xa8); /* TAY */
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR + 2, 0x68); /* PLA */
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR + 3, 0xaa); /* TAX */
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR + 4, 0x68); /* PLA */
|
|
dPutByte(RETURN_FROM_PLAYER_ADDR + 5, 0x40); /* RTI */
|
|
ast _ cpu_pc = player;
|
|
}
|
|
Cpu_RunScanlines(ast, ast _ module_info->fastplay);
|
|
break;
|
|
case ASAP_TYPE_SAP_S:
|
|
Cpu_RunScanlines(ast, ast _ module_info->fastplay);
|
|
{
|
|
V(int, i) = dGetByte(0x45) - 1;
|
|
dPutByte(0x45, i);
|
|
if (i == 0)
|
|
dPutByte(0xb07b, dGetByte(0xb07b) + 1);
|
|
}
|
|
break;
|
|
#ifndef ASAP_ONLY_SAP
|
|
case ASAP_TYPE_DLT:
|
|
call_6502(ast, player + 0x103, ast _ module_info->fastplay);
|
|
break;
|
|
case ASAP_TYPE_MPT:
|
|
case ASAP_TYPE_RMT:
|
|
case ASAP_TYPE_TM2:
|
|
call_6502(ast, player + 3, ast _ module_info->fastplay);
|
|
break;
|
|
case ASAP_TYPE_TMC:
|
|
if (--ast _ tmc_per_frame_counter <= 0) {
|
|
ast _ tmc_per_frame_counter = ast _ tmc_per_frame;
|
|
call_6502(ast, player + 3, ast _ module_info->fastplay);
|
|
}
|
|
else
|
|
call_6502(ast, player + 6, ast _ module_info->fastplay);
|
|
break;
|
|
#endif
|
|
}
|
|
PokeySound_EndFrame(ast, ast _ module_info->fastplay * 114);
|
|
if (ast _ silence_cycles > 0) {
|
|
if (PokeySound_IsSilent(ADDRESSOF ast _ base_pokey)
|
|
&& PokeySound_IsSilent(ADDRESSOF ast _ extra_pokey)) {
|
|
ast _ silence_cycles_counter -= ast _ module_info->fastplay * 114;
|
|
if (ast _ silence_cycles_counter <= 0)
|
|
return FALSE;
|
|
}
|
|
else
|
|
ast _ silence_cycles_counter = ast _ silence_cycles;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
FUNC(int, ASAP_GetPosition, (P(CONST ASAP_State PTR, ast)))
|
|
{
|
|
return ast _ blocks_played * 10 / (ASAP_SAMPLE_RATE / 100);
|
|
}
|
|
|
|
FUNC(int, milliseconds_to_blocks, (P(int, milliseconds)))
|
|
{
|
|
return milliseconds * (ASAP_SAMPLE_RATE / 100) / 10;
|
|
}
|
|
|
|
#ifndef ACTIONSCRIPT
|
|
|
|
FUNC(void, ASAP_Seek, (P(ASAP_State PTR, ast), P(int, position)))
|
|
{
|
|
V(int, block) = milliseconds_to_blocks(position);
|
|
if (block < ast _ blocks_played)
|
|
ASAP_PlaySong(ast, ast _ current_song, ast _ current_duration);
|
|
while (ast _ blocks_played + ast _ samples - ast _ sample_index < block) {
|
|
ast _ blocks_played += ast _ samples - ast _ sample_index;
|
|
call_6502_player(ast);
|
|
}
|
|
ast _ sample_index += block - ast _ blocks_played;
|
|
ast _ blocks_played = block;
|
|
}
|
|
|
|
PRIVATE FUNC(void, serialize_int, (P(BYTEARRAY, buffer), P(int, offset), P(int, value)))
|
|
{
|
|
buffer[offset] = TO_BYTE(value);
|
|
buffer[offset + 1] = TO_BYTE(value >> 8);
|
|
buffer[offset + 2] = TO_BYTE(value >> 16);
|
|
buffer[offset + 3] = TO_BYTE(value >> 24);
|
|
}
|
|
|
|
FUNC(void, ASAP_GetWavHeader, (
|
|
P(CONST ASAP_State PTR, ast), P(BYTEARRAY, buffer), P(ASAP_SampleFormat, format)))
|
|
{
|
|
V(int, use_16bit) = format != ASAP_FORMAT_U8 ? 1 : 0;
|
|
V(int, block_size) = ast _ module_info->channels << use_16bit;
|
|
V(int, bytes_per_second) = ASAP_SAMPLE_RATE * block_size;
|
|
V(int, total_blocks) = milliseconds_to_blocks(ast _ current_duration);
|
|
V(int, n_bytes) = (total_blocks - ast _ blocks_played) * block_size;
|
|
buffer[0] = CAST(byte) CHARCODE('R');
|
|
buffer[1] = CAST(byte) CHARCODE('I');
|
|
buffer[2] = CAST(byte) CHARCODE('F');
|
|
buffer[3] = CAST(byte) CHARCODE('F');
|
|
serialize_int(buffer, 4, n_bytes + 36);
|
|
buffer[8] = CAST(byte) CHARCODE('W');
|
|
buffer[9] = CAST(byte) CHARCODE('A');
|
|
buffer[10] = CAST(byte) CHARCODE('V');
|
|
buffer[11] = CAST(byte) CHARCODE('E');
|
|
buffer[12] = CAST(byte) CHARCODE('f');
|
|
buffer[13] = CAST(byte) CHARCODE('m');
|
|
buffer[14] = CAST(byte) CHARCODE('t');
|
|
buffer[15] = CAST(byte) CHARCODE(' ');
|
|
buffer[16] = 16;
|
|
buffer[17] = 0;
|
|
buffer[18] = 0;
|
|
buffer[19] = 0;
|
|
buffer[20] = 1;
|
|
buffer[21] = 0;
|
|
buffer[22] = CAST(byte) ast _ module_info->channels;
|
|
buffer[23] = 0;
|
|
serialize_int(buffer, 24, ASAP_SAMPLE_RATE);
|
|
serialize_int(buffer, 28, bytes_per_second);
|
|
buffer[32] = CAST(byte) block_size;
|
|
buffer[33] = 0;
|
|
buffer[34] = CAST(byte) (8 << use_16bit);
|
|
buffer[35] = 0;
|
|
buffer[36] = CAST(byte) CHARCODE('d');
|
|
buffer[37] = CAST(byte) CHARCODE('a');
|
|
buffer[38] = CAST(byte) CHARCODE('t');
|
|
buffer[39] = CAST(byte) CHARCODE('a');
|
|
serialize_int(buffer, 40, n_bytes);
|
|
}
|
|
|
|
#endif /* ACTIONSCRIPT */
|
|
|
|
PRIVATE FUNC(int, ASAP_GenerateAt, (P(ASAP_State PTR, ast), P(VOIDPTR, buffer), P(int, buffer_offset), P(int, buffer_len), P(ASAP_SampleFormat, format)))
|
|
{
|
|
V(int, block_shift);
|
|
V(int, buffer_blocks);
|
|
V(int, block);
|
|
if (ast _ silence_cycles > 0 && ast _ silence_cycles_counter <= 0)
|
|
return 0;
|
|
#ifdef ACTIONSCRIPT
|
|
block_shift = 0;
|
|
#else
|
|
block_shift = (ast _ module_info->channels - 1) + (format != ASAP_FORMAT_U8 ? 1 : 0);
|
|
#endif
|
|
buffer_blocks = buffer_len >> block_shift;
|
|
if (ast _ current_duration > 0) {
|
|
V(int, total_blocks) = milliseconds_to_blocks(ast _ current_duration);
|
|
if (buffer_blocks > total_blocks - ast _ blocks_played)
|
|
buffer_blocks = total_blocks - ast _ blocks_played;
|
|
}
|
|
block = 0;
|
|
do {
|
|
V(int, blocks) = PokeySound_Generate(ast, CAST(BYTEARRAY) buffer,
|
|
buffer_offset + (block << block_shift), buffer_blocks - block, format);
|
|
ast _ blocks_played += blocks;
|
|
block += blocks;
|
|
} while (block < buffer_blocks && call_6502_player(ast));
|
|
return block << block_shift;
|
|
}
|
|
|
|
FUNC(int, ASAP_Generate, (P(ASAP_State PTR, ast), P(VOIDPTR, buffer), P(int, buffer_len), P(ASAP_SampleFormat, format)))
|
|
{
|
|
return ASAP_GenerateAt(ast, buffer, 0, buffer_len, format);
|
|
}
|
|
|
|
#endif /* ASAP_ONLY_INFO */
|
|
|
|
#ifdef C
|
|
|
|
abool ASAP_CanSetModuleInfo(const char *filename)
|
|
{
|
|
int ext = get_packed_ext(filename);
|
|
return ext == ASAP_EXT('S', 'A', 'P');
|
|
}
|
|
|
|
abool ASAP_ChangeExt(char *filename, const char *ext)
|
|
{
|
|
char *dest = NULL;
|
|
while (*filename != '\0') {
|
|
if (*filename == '/' || *filename == '\\')
|
|
dest = NULL;
|
|
else if (*filename == '.')
|
|
dest = filename + 1;
|
|
filename++;
|
|
}
|
|
if (dest == NULL)
|
|
return FALSE;
|
|
strcpy(dest, ext);
|
|
return TRUE;
|
|
}
|
|
|
|
static byte *put_string(byte *dest, const char *str)
|
|
{
|
|
while (*str != '\0')
|
|
*dest++ = *str++;
|
|
return dest;
|
|
}
|
|
|
|
static byte *put_dec(byte *dest, int value)
|
|
{
|
|
if (value >= 10) {
|
|
dest = put_dec(dest, value / 10);
|
|
value %= 10;
|
|
}
|
|
*dest++ = '0' + value;
|
|
return dest;
|
|
}
|
|
|
|
static byte *put_text_tag(byte *dest, const char *tag, const char *value)
|
|
{
|
|
dest = put_string(dest, tag);
|
|
*dest++ = '"';
|
|
if (*value == '\0')
|
|
value = "<?>";
|
|
while (*value != '\0') {
|
|
if (*value < ' ' || *value > 'z' || *value == '"' || *value == '`')
|
|
return NULL;
|
|
*dest++ = *value++;
|
|
}
|
|
*dest++ = '"';
|
|
*dest++ = '\r';
|
|
*dest++ = '\n';
|
|
return dest;
|
|
}
|
|
|
|
static byte *put_dec_tag(byte *dest, const char *tag, int value)
|
|
{
|
|
dest = put_string(dest, tag);
|
|
dest = put_dec(dest, value);
|
|
*dest++ = '\r';
|
|
*dest++ = '\n';
|
|
return dest;
|
|
}
|
|
|
|
static byte *start_sap_header(byte *dest, const ASAP_ModuleInfo *module_info)
|
|
{
|
|
dest = put_string(dest, "SAP\r\n");
|
|
dest = put_text_tag(dest, "AUTHOR ", module_info->author);
|
|
if (dest == NULL)
|
|
return NULL;
|
|
dest = put_text_tag(dest, "NAME ", module_info->name);
|
|
if (dest == NULL)
|
|
return NULL;
|
|
dest = put_text_tag(dest, "DATE ", module_info->date);
|
|
if (dest == NULL)
|
|
return NULL;
|
|
if (module_info->songs > 1) {
|
|
dest = put_dec_tag(dest, "SONGS ", module_info->songs);
|
|
if (module_info->default_song > 0)
|
|
dest = put_dec_tag(dest, "DEFSONG ", module_info->default_song);
|
|
}
|
|
if (module_info->channels > 1)
|
|
dest = put_string(dest, "STEREO\r\n");
|
|
return dest;
|
|
}
|
|
|
|
static char *two_digits(char *s, int x)
|
|
{
|
|
s[0] = '0' + x / 10;
|
|
s[1] = '0' + x % 10;
|
|
return s + 2;
|
|
}
|
|
|
|
void ASAP_DurationToString(char *s, int duration)
|
|
{
|
|
if (duration >= 0 && duration < 100 * 60 * 1000) {
|
|
int seconds = duration / 1000;
|
|
s = two_digits(s, seconds / 60);
|
|
*s++ = ':';
|
|
s = two_digits(s, seconds % 60);
|
|
duration %= 1000;
|
|
if (duration != 0) {
|
|
*s++ = '.';
|
|
s = two_digits(s, duration / 10);
|
|
duration %= 10;
|
|
if (duration != 0)
|
|
*s++ = '0' + duration;
|
|
}
|
|
}
|
|
*s = '\0';
|
|
}
|
|
|
|
static byte *put_durations(byte *dest, const ASAP_ModuleInfo *module_info)
|
|
{
|
|
int song;
|
|
for (song = 0; song < module_info->songs; song++) {
|
|
if (module_info->durations[song] < 0)
|
|
break;
|
|
dest = put_string(dest, "TIME ");
|
|
ASAP_DurationToString((char *) dest, module_info->durations[song]);
|
|
while (*dest != '\0')
|
|
dest++;
|
|
if (module_info->loops[song])
|
|
dest = put_string(dest, " LOOP");
|
|
*dest++ = '\r';
|
|
*dest++ = '\n';
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
int ASAP_SetModuleInfo(const ASAP_ModuleInfo *module_info, const BYTEARRAY module, int module_len, BYTEARRAY out_module)
|
|
{
|
|
byte *dest;
|
|
int i;
|
|
if (memcmp(module, "SAP\r\n", 5) != 0)
|
|
return -1;
|
|
dest = start_sap_header(out_module, module_info);
|
|
if (dest == NULL)
|
|
return -1;
|
|
i = 5;
|
|
while (i < module_len && module[i] != 0xff) {
|
|
if (memcmp(module + i, "AUTHOR ", 7) == 0
|
|
|| memcmp(module + i, "NAME ", 5) == 0
|
|
|| memcmp(module + i, "DATE ", 5) == 0
|
|
|| memcmp(module + i, "SONGS ", 6) == 0
|
|
|| memcmp(module + i, "DEFSONG ", 8) == 0
|
|
|| memcmp(module + i, "STEREO\r", 7) == 0
|
|
|| memcmp(module + i, "TIME ", 5) == 0) {
|
|
while (i < module_len && module[i++] != 0x0a);
|
|
}
|
|
else {
|
|
int b;
|
|
do {
|
|
b = module[i++];
|
|
*dest++ = b;
|
|
} while (i < module_len && b != 0x0a);
|
|
}
|
|
}
|
|
dest = put_durations(dest, module_info);
|
|
module_len -= i;
|
|
memcpy(dest, module + i, module_len);
|
|
dest += module_len;
|
|
return dest - out_module;
|
|
}
|
|
|
|
#if !defined(ASAP_ONLY_SAP) && !defined(ASAP_ONLY_INFO)
|
|
|
|
#define RMT_INIT 0x0c80
|
|
#define TM2_INIT 0x1080
|
|
|
|
const char *ASAP_CanConvert(
|
|
const char *filename, const ASAP_ModuleInfo *module_info,
|
|
const BYTEARRAY module, int module_len)
|
|
{
|
|
(void) filename;
|
|
switch (module_info->type) {
|
|
case ASAP_TYPE_SAP_B:
|
|
if ((module_info->init == 0x3fb || module_info->init == 0x3f9) && module_info->player == 0x503)
|
|
return "dlt";
|
|
if (module_info->init == 0x4f3 || module_info->init == 0xf4f3 || module_info->init == 0x4ef)
|
|
return module_info->fastplay == 156 ? "mpd" : "mpt";
|
|
if (module_info->init == RMT_INIT)
|
|
return "rmt";
|
|
if ((module_info->init == 0x4f5 || module_info->init == 0xf4f5 || module_info->init == 0x4f2)
|
|
|| ((module_info->init == 0x4e7 || module_info->init == 0xf4e7 || module_info->init == 0x4e4) && module_info->fastplay == 156)
|
|
|| ((module_info->init == 0x4e5 || module_info->init == 0xf4e5 || module_info->init == 0x4e2) && (module_info->fastplay == 104 || module_info->fastplay == 78)))
|
|
return "tmc";
|
|
if (module_info->init == TM2_INIT)
|
|
return "tm2";
|
|
break;
|
|
case ASAP_TYPE_SAP_C:
|
|
if (module_info->player == 0x500 || module_info->player == 0xf500) {
|
|
if (module_info->fastplay == 156)
|
|
return "dmc";
|
|
if (module_info->channels > 1)
|
|
return "cms";
|
|
if (module[module_len - 170] == 0x1e)
|
|
return "cmr";
|
|
if (module[module_len - 909] == 0x30)
|
|
return "cm3";
|
|
return "cmc";
|
|
}
|
|
break;
|
|
case ASAP_TYPE_CMC:
|
|
case ASAP_TYPE_CM3:
|
|
case ASAP_TYPE_CMR:
|
|
case ASAP_TYPE_CMS:
|
|
case ASAP_TYPE_DLT:
|
|
case ASAP_TYPE_MPT:
|
|
case ASAP_TYPE_RMT:
|
|
case ASAP_TYPE_TMC:
|
|
case ASAP_TYPE_TM2:
|
|
return "sap";
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static byte *put_hex_tag(byte *dest, const char *tag, int value)
|
|
{
|
|
int i;
|
|
if (value < 0)
|
|
return dest;
|
|
dest = put_string(dest, tag);
|
|
for (i = 12; i >= 0; i -= 4) {
|
|
int digit = (value >> i) & 0xf;
|
|
*dest++ = (byte) (digit + (digit < 10 ? '0' : 'A' - 10));
|
|
}
|
|
*dest++ = '\r';
|
|
*dest++ = '\n';
|
|
return dest;
|
|
}
|
|
|
|
static byte *put_sap_header(byte *dest, const ASAP_ModuleInfo *module_info, char type, int music, int init, int player)
|
|
{
|
|
dest = start_sap_header(dest, module_info);
|
|
if (dest == NULL)
|
|
return NULL;
|
|
dest = put_string(dest, "TYPE ");
|
|
*dest++ = type;
|
|
*dest++ = '\r';
|
|
*dest++ = '\n';
|
|
if (module_info->fastplay != 312)
|
|
dest = put_dec_tag(dest, "FASTPLAY ", module_info->fastplay);
|
|
dest = put_hex_tag(dest, "MUSIC ", music);
|
|
dest = put_hex_tag(dest, "INIT ", init);
|
|
dest = put_hex_tag(dest, "PLAYER ", player);
|
|
dest = put_durations(dest, module_info);
|
|
return dest;
|
|
}
|
|
|
|
int ASAP_Convert(
|
|
const char *filename, const ASAP_ModuleInfo *module_info,
|
|
const BYTEARRAY module, int module_len, BYTEARRAY out_module)
|
|
{
|
|
(void) filename;
|
|
int out_len;
|
|
byte *dest;
|
|
int addr;
|
|
int player;
|
|
static const int tmc_player[4] = { 3, -9, -10, -10 };
|
|
static const int tmc_init[4] = { -14, -16, -17, -17 };
|
|
switch (module_info->type) {
|
|
case ASAP_TYPE_SAP_B:
|
|
case ASAP_TYPE_SAP_C:
|
|
out_len = UWORD(module, module_info->header_len + 4) - UWORD(module, module_info->header_len + 2) + 7;
|
|
if (out_len < 7 || module_info->header_len + out_len >= module_len)
|
|
return -1;
|
|
memcpy(out_module, module + module_info->header_len, out_len);
|
|
return out_len;
|
|
case ASAP_TYPE_CMC:
|
|
case ASAP_TYPE_CM3:
|
|
case ASAP_TYPE_CMR:
|
|
case ASAP_TYPE_CMS:
|
|
dest = put_sap_header(out_module, module_info, 'C', module_info->music, -1, module_info->player);
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
dest[0] = 0xff; /* some modules start with zeros */
|
|
dest[1] = 0xff;
|
|
dest += module_len;
|
|
if (module_info->type == ASAP_TYPE_CM3) {
|
|
memcpy(dest, cm3_obx + 2, sizeof(cm3_obx) - 2);
|
|
dest += sizeof(cm3_obx) - 2;
|
|
}
|
|
else if (module_info->type == ASAP_TYPE_CMS) {
|
|
memcpy(dest, cms_obx + 2, sizeof(cms_obx) - 2);
|
|
dest += sizeof(cms_obx) - 2;
|
|
}
|
|
else {
|
|
memcpy(dest, cmc_obx + 2, sizeof(cmc_obx) - 2);
|
|
if (module_info->type == ASAP_TYPE_CMR)
|
|
memcpy(dest + 4 + CMR_BASS_TABLE_OFFSET, cmr_bass_table, sizeof(cmr_bass_table));
|
|
dest += sizeof(cmc_obx) - 2;
|
|
}
|
|
return dest - out_module;
|
|
case ASAP_TYPE_DLT:
|
|
if (module_info->songs != 1) {
|
|
addr = module_info->player - 7 - module_info->songs;
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, module_info->player - 7, module_info->player + 0x103);
|
|
}
|
|
else {
|
|
addr = module_info->player - 5;
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, addr, module_info->player + 0x103);
|
|
}
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
if (module_len == 0x2c06) {
|
|
dest[4] = 0;
|
|
dest[5] = 0x4c;
|
|
dest[0x2c06] = 0;
|
|
}
|
|
dest += 0x2c07;
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
*dest++ = dlt_obx[4];
|
|
*dest++ = dlt_obx[5];
|
|
if (module_info->songs != 1) {
|
|
memcpy(dest, module_info->song_pos, module_info->songs);
|
|
dest += module_info->songs;
|
|
*dest++ = 0xaa; /* tax */
|
|
*dest++ = 0xbc; /* ldy song2pos,x */
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
}
|
|
else {
|
|
*dest++ = 0xa0; /* ldy #0 */
|
|
*dest++ = 0;
|
|
}
|
|
*dest++ = 0x4c; /* jmp init */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) ((module_info->player >> 8) + 1);
|
|
memcpy(dest, dlt_obx + 6, sizeof(dlt_obx) - 6);
|
|
dest += sizeof(dlt_obx) - 6;
|
|
return dest - out_module;
|
|
case ASAP_TYPE_MPT:
|
|
if (module_info->songs != 1) {
|
|
addr = module_info->player - 17 - module_info->songs;
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, module_info->player - 17, module_info->player + 3);
|
|
}
|
|
else {
|
|
addr = module_info->player - 13;
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, addr, module_info->player + 3);
|
|
}
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
dest += module_len;
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
*dest++ = mpt_obx[4];
|
|
*dest++ = mpt_obx[5];
|
|
if (module_info->songs != 1) {
|
|
memcpy(dest, module_info->song_pos, module_info->songs);
|
|
dest += module_info->songs;
|
|
*dest++ = 0x48; /* pha */
|
|
}
|
|
*dest++ = 0xa0; /* ldy #<music */
|
|
*dest++ = (byte) module_info->music;
|
|
*dest++ = 0xa2; /* ldx #>music */
|
|
*dest++ = (byte) (module_info->music >> 8);
|
|
*dest++ = 0xa9; /* lda #0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0x20; /* jsr player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
if (module_info->songs != 1) {
|
|
*dest++ = 0x68; /* pla */
|
|
*dest++ = 0xa8; /* tay */
|
|
*dest++ = 0xbe; /* ldx song2pos,y */
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
}
|
|
else {
|
|
*dest++ = 0xa2; /* ldx #0 */
|
|
*dest++ = 0;
|
|
}
|
|
*dest++ = 0xa9; /* lda #2 */
|
|
*dest++ = 2;
|
|
memcpy(dest, mpt_obx + 6, sizeof(mpt_obx) - 6);
|
|
dest += sizeof(mpt_obx) - 6;
|
|
return dest - out_module;
|
|
case ASAP_TYPE_RMT:
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, RMT_INIT, module_info->player + 3);
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
dest += module_len;
|
|
*dest++ = (byte) RMT_INIT;
|
|
*dest++ = (byte) (RMT_INIT >> 8);
|
|
if (module_info->songs != 1) {
|
|
addr = RMT_INIT + 10 + module_info->songs;
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
*dest++ = 0xa8; /* tay */
|
|
*dest++ = 0xb9; /* lda song2pos,y */
|
|
*dest++ = (byte) (RMT_INIT + 11);
|
|
*dest++ = (byte) ((RMT_INIT + 11) >> 8);
|
|
}
|
|
else {
|
|
*dest++ = (byte) (RMT_INIT + 8);
|
|
*dest++ = (byte) ((RMT_INIT + 8) >> 8);
|
|
*dest++ = 0xa9; /* lda #0 */
|
|
*dest++ = 0;
|
|
}
|
|
*dest++ = 0xa2; /* ldx #<music */
|
|
*dest++ = (byte) module_info->music;
|
|
*dest++ = 0xa0; /* ldy #>music */
|
|
*dest++ = (byte) (module_info->music >> 8);
|
|
*dest++ = 0x4c; /* jmp player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
if (module_info->songs != 1) {
|
|
memcpy(dest, module_info->song_pos, module_info->songs);
|
|
dest += module_info->songs;
|
|
}
|
|
if (module_info->channels == 1) {
|
|
memcpy(dest, rmt4_obx + 2, sizeof(rmt4_obx) - 2);
|
|
dest += sizeof(rmt4_obx) - 2;
|
|
}
|
|
else {
|
|
memcpy(dest, rmt8_obx + 2, sizeof(rmt8_obx) - 2);
|
|
dest += sizeof(rmt8_obx) - 2;
|
|
}
|
|
return dest - out_module;
|
|
case ASAP_TYPE_TMC:
|
|
player = module_info->player + tmc_player[module[0x25] - 1];
|
|
addr = player + tmc_init[module[0x25] - 1];
|
|
if (module_info->songs != 1)
|
|
addr -= 3;
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, addr, player);
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
dest += module_len;
|
|
*dest++ = (byte) addr;
|
|
*dest++ = (byte) (addr >> 8);
|
|
*dest++ = tmc_obx[4];
|
|
*dest++ = tmc_obx[5];
|
|
if (module_info->songs != 1)
|
|
*dest++ = 0x48; /* pha */
|
|
*dest++ = 0xa0; /* ldy #<music */
|
|
*dest++ = (byte) module_info->music;
|
|
*dest++ = 0xa2; /* ldx #>music */
|
|
*dest++ = (byte) (module_info->music >> 8);
|
|
*dest++ = 0xa9; /* lda #$70 */
|
|
*dest++ = 0x70;
|
|
*dest++ = 0x20; /* jsr player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
if (module_info->songs != 1) {
|
|
*dest++ = 0x68; /* pla */
|
|
*dest++ = 0xaa; /* tax */
|
|
*dest++ = 0xa9; /* lda #0 */
|
|
*dest++ = 0;
|
|
}
|
|
else {
|
|
*dest++ = 0xa9; /* lda #$60 */
|
|
*dest++ = 0x60;
|
|
}
|
|
switch (module[0x25]) {
|
|
case 2:
|
|
*dest++ = 0x06; /* asl 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0x4c; /* jmp player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
*dest++ = 0xa5; /* lda 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0xe6; /* inc 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0x4a; /* lsr @ */
|
|
*dest++ = 0x90; /* bcc player+3 */
|
|
*dest++ = 5;
|
|
*dest++ = 0xb0; /* bcs player+6 */
|
|
*dest++ = 6;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
*dest++ = 0xa0; /* ldy #1 */
|
|
*dest++ = 1;
|
|
*dest++ = 0x84; /* sty 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0xd0; /* bne player */
|
|
*dest++ = 10;
|
|
*dest++ = 0xc6; /* dec 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0xd0; /* bne player+6 */
|
|
*dest++ = 12;
|
|
*dest++ = 0xa0; /* ldy #3 */
|
|
*dest++ = module[0x25];
|
|
*dest++ = 0x84; /* sty 0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0xd0; /* bne player+3 */
|
|
*dest++ = 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
memcpy(dest, tmc_obx + 6, sizeof(tmc_obx) - 6);
|
|
dest += sizeof(tmc_obx) - 6;
|
|
return dest - out_module;
|
|
case ASAP_TYPE_TM2:
|
|
dest = put_sap_header(out_module, module_info, 'B', -1, TM2_INIT, module_info->player + 3);
|
|
if (dest == NULL)
|
|
return -1;
|
|
memcpy(dest, module, module_len);
|
|
dest += module_len;
|
|
*dest++ = (byte) TM2_INIT;
|
|
*dest++ = (byte) (TM2_INIT >> 8);
|
|
if (module_info->songs != 1) {
|
|
*dest++ = (byte) (TM2_INIT + 16);
|
|
*dest++ = (byte) ((TM2_INIT + 16) >> 8);
|
|
*dest++ = 0x48; /* pha */
|
|
}
|
|
else {
|
|
*dest++ = (byte) (TM2_INIT + 14);
|
|
*dest++ = (byte) ((TM2_INIT + 14) >> 8);
|
|
}
|
|
*dest++ = 0xa0; /* ldy #<music */
|
|
*dest++ = (byte) module_info->music;
|
|
*dest++ = 0xa2; /* ldx #>music */
|
|
*dest++ = (byte) (module_info->music >> 8);
|
|
*dest++ = 0xa9; /* lda #$70 */
|
|
*dest++ = 0x70;
|
|
*dest++ = 0x20; /* jsr player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
if (module_info->songs != 1) {
|
|
*dest++ = 0x68; /* pla */
|
|
*dest++ = 0xaa; /* tax */
|
|
*dest++ = 0xa9; /* lda #0 */
|
|
*dest++ = 0;
|
|
}
|
|
else {
|
|
*dest++ = 0xa9; /* lda #0 */
|
|
*dest++ = 0;
|
|
*dest++ = 0xaa; /* tax */
|
|
}
|
|
*dest++ = 0x4c; /* jmp player */
|
|
*dest++ = (byte) module_info->player;
|
|
*dest++ = (byte) (module_info->player >> 8);
|
|
memcpy(dest, tm2_obx + 2, sizeof(tm2_obx) - 2);
|
|
dest += sizeof(tm2_obx) - 2;
|
|
return dest - out_module;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#endif /* !defined(ASAP_ONLY_SAP) && !defined(ASAP_ONLY_INFO) */
|
|
|
|
static abool has_two_digits(const char *s)
|
|
{
|
|
return s[0] >= '0' && s[0] <= '9' && s[1] >= '0' && s[1] <= '9';
|
|
}
|
|
|
|
/* "DD/MM/YYYY", "MM/YYYY", "YYYY" -> "YYYY" */
|
|
abool ASAP_DateToYear(const char *date, char *year)
|
|
{
|
|
if (!has_two_digits(date))
|
|
return FALSE;
|
|
if (date[2] == '/') {
|
|
date += 3;
|
|
if (!has_two_digits(date))
|
|
return FALSE;
|
|
if (date[2] == '/') {
|
|
date += 3;
|
|
if (!has_two_digits(date))
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (!has_two_digits(date + 2) || date[4] != '\0')
|
|
return FALSE;
|
|
memcpy(year, date, 5);
|
|
return TRUE;
|
|
}
|
|
|
|
#endif /* C */
|