Components not affected by CGB’s double speed mode now operate in 8MHz mode to theoretically make advance_cycles(gb, 1) safe.
This commit is contained in:
parent
d0202a3f9a
commit
9802ca41dd
@ -385,7 +385,7 @@ void GB_apu_run(GB_gameboy_t *gb)
|
|||||||
|
|
||||||
if (gb->apu_output.sample_rate) {
|
if (gb->apu_output.sample_rate) {
|
||||||
gb->apu_output.cycles_since_render += cycles;
|
gb->apu_output.cycles_since_render += cycles;
|
||||||
double cycles_per_sample = GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate;
|
double cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
|
||||||
|
|
||||||
if (gb->apu_output.sample_cycles > cycles_per_sample) {
|
if (gb->apu_output.sample_cycles > cycles_per_sample) {
|
||||||
gb->apu_output.sample_cycles -= cycles_per_sample;
|
gb->apu_output.sample_cycles -= cycles_per_sample;
|
||||||
|
@ -127,7 +127,7 @@ typedef struct {
|
|||||||
volatile bool copy_in_progress;
|
volatile bool copy_in_progress;
|
||||||
volatile bool lock;
|
volatile bool lock;
|
||||||
|
|
||||||
double sample_cycles;
|
double sample_cycles; // In 8 MHz units
|
||||||
|
|
||||||
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
|
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
|
||||||
unsigned cycles_since_render;
|
unsigned cycles_since_render;
|
||||||
|
@ -1411,8 +1411,8 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||||||
GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled");
|
GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled");
|
||||||
|
|
||||||
|
|
||||||
GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles);
|
GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles / 2);
|
||||||
GB_log(gb, "Current line: %d\n", gb->display_cycles / 456);
|
GB_log(gb, "Current line: %d\n", gb->display_cycles / 456 / 2);
|
||||||
GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]);
|
GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]);
|
||||||
GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]);
|
GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]);
|
||||||
GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]);
|
GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]);
|
||||||
|
@ -15,13 +15,15 @@
|
|||||||
Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt
|
Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define MODE2_LENGTH (80)
|
/* The display (logically) runs in 8MHz units, so we double our length constants */
|
||||||
#define MODE3_LENGTH (172)
|
#define MODE2_LENGTH (80 * 2)
|
||||||
#define MODE0_LENGTH (204)
|
#define MODE3_LENGTH (172 * 2)
|
||||||
|
#define MODE0_LENGTH (204 * 2)
|
||||||
#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456
|
#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456
|
||||||
#define LINES (144)
|
#define LINES (144)
|
||||||
#define WIDTH (160)
|
#define WIDTH (160)
|
||||||
#define VIRTUAL_LINES (LCDC_PERIOD / LINE_LENGTH) // = 154
|
#define FRAME_LENGTH (LCDC_PERIOD * 2)
|
||||||
|
#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154
|
||||||
|
|
||||||
typedef struct __attribute__((packed)) {
|
typedef struct __attribute__((packed)) {
|
||||||
uint8_t y;
|
uint8_t y;
|
||||||
@ -332,9 +334,9 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
|
|
||||||
/* Keep sending vblanks to user even if the screen is off */
|
/* Keep sending vblanks to user even if the screen is off */
|
||||||
gb->display_cycles += cycles;
|
gb->display_cycles += cycles;
|
||||||
if (gb->display_cycles >= LCDC_PERIOD) {
|
if (gb->display_cycles >= FRAME_LENGTH) {
|
||||||
/* VBlank! */
|
/* VBlank! */
|
||||||
gb->display_cycles -= LCDC_PERIOD;
|
gb->display_cycles -= FRAME_LENGTH;
|
||||||
display_vblank(gb);
|
display_vblank(gb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,23 +346,23 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t atomic_increase = gb->cgb_double_speed? 2 : 4;
|
uint8_t atomic_increase = gb->cgb_double_speed? 4 : 8;
|
||||||
/* According to AntonioND's docs this value should be 0 in CGB mode, but tests I ran on my CGB seem to contradict
|
/* According to AntonioND's docs this value should be 0 in CGB mode, but tests I ran on my CGB seem to contradict
|
||||||
these findings.
|
these findings.
|
||||||
Todo: Investigate what causes the difference between our findings */
|
Todo: Investigate what causes the difference between our findings */
|
||||||
uint8_t stat_delay = gb->cgb_double_speed? 2 : 4; // (gb->cgb_mode? 0 : 4);
|
uint8_t stat_delay = gb->cgb_double_speed? 4 : 8; // (gb->cgb_mode? 0 : 8);
|
||||||
/* Todo: Is this correct for DMG mode CGB? */
|
/* Todo: Is this correct for DMG mode CGB? */
|
||||||
uint8_t scx_delay = gb->effective_scx & 7;
|
uint8_t scx_delay = (gb->effective_scx & 7) * 2;
|
||||||
if (gb->cgb_double_speed) {
|
if (gb->cgb_double_speed) {
|
||||||
scx_delay = (scx_delay + 1) & ~1;
|
scx_delay = (scx_delay + 2) & ~3;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
scx_delay = (scx_delay + (gb->first_scanline ? 2 : 0)) & ~3;
|
scx_delay = (scx_delay + (gb->first_scanline ? 4 : 0)) & ~7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */
|
/* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */
|
||||||
uint8_t oam_blocking_rush = gb->cgb_double_speed? 2 : 4;
|
uint8_t oam_blocking_rush = gb->cgb_double_speed? 4 : 8;
|
||||||
uint8_t vram_blocking_rush = gb->is_cgb? 0 : 4;
|
uint8_t vram_blocking_rush = gb->is_cgb? 0 : 8;
|
||||||
|
|
||||||
for (; cycles; cycles -= atomic_increase) {
|
for (; cycles; cycles -= atomic_increase) {
|
||||||
bool dmg_future_stat = false;
|
bool dmg_future_stat = false;
|
||||||
@ -371,11 +373,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->stat_interrupt_line = false;
|
gb->stat_interrupt_line = false;
|
||||||
|
|
||||||
gb->display_cycles += atomic_increase;
|
gb->display_cycles += atomic_increase;
|
||||||
/* The very first line is 4 clocks shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and
|
/* The very first line is 1 M-cycle shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and
|
||||||
CGB in double speed mode. */
|
CGB in double speed mode. */
|
||||||
if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 8) {
|
if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 0x10) {
|
||||||
gb->first_scanline = false;
|
gb->first_scanline = false;
|
||||||
gb->display_cycles += 4;
|
gb->display_cycles += 8;
|
||||||
}
|
}
|
||||||
bool should_compare_ly = true;
|
bool should_compare_ly = true;
|
||||||
uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH;
|
uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH;
|
||||||
@ -383,7 +385,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
|
|
||||||
|
|
||||||
/* Handle cycle completion. STAT's initial value depends on model and mode */
|
/* Handle cycle completion. STAT's initial value depends on model and mode */
|
||||||
if (gb->display_cycles == LCDC_PERIOD) {
|
if (gb->display_cycles == FRAME_LENGTH) {
|
||||||
/* VBlank! */
|
/* VBlank! */
|
||||||
gb->display_cycles = 0;
|
gb->display_cycles = 0;
|
||||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||||
@ -542,11 +544,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
case 0:
|
case 0:
|
||||||
should_compare_ly = false;
|
should_compare_ly = false;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 8:
|
||||||
gb->io_registers[GB_IO_LY] = 0;
|
gb->io_registers[GB_IO_LY] = 0;
|
||||||
ly_for_comparison = VIRTUAL_LINES - 1;
|
ly_for_comparison = VIRTUAL_LINES - 1;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 16:
|
||||||
gb->io_registers[GB_IO_LY] = 0;
|
gb->io_registers[GB_IO_LY] = 0;
|
||||||
should_compare_ly = false;
|
should_compare_ly = false;
|
||||||
break;
|
break;
|
||||||
@ -561,9 +563,9 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
case 0:
|
case 0:
|
||||||
ly_for_comparison = VIRTUAL_LINES - 2;
|
ly_for_comparison = VIRTUAL_LINES - 2;
|
||||||
break;
|
break;
|
||||||
case 4:
|
|
||||||
break;
|
|
||||||
case 8:
|
case 8:
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
gb->io_registers[GB_IO_LY] = 0;
|
gb->io_registers[GB_IO_LY] = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -576,7 +578,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
|
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 8:
|
||||||
gb->io_registers[GB_IO_LY] = 0;
|
gb->io_registers[GB_IO_LY] = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -591,11 +593,11 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
case 0:
|
case 0:
|
||||||
ly_for_comparison = VIRTUAL_LINES - 2;
|
ly_for_comparison = VIRTUAL_LINES - 2;
|
||||||
break;
|
break;
|
||||||
case 2:
|
|
||||||
case 4:
|
case 4:
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
case 8:
|
case 8:
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
case 16:
|
||||||
gb->io_registers[GB_IO_LY] = 0;
|
gb->io_registers[GB_IO_LY] = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -666,7 +668,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
This is based on AntonioND's docs, however I could not reproduce these findings on my CGB.
|
This is based on AntonioND's docs, however I could not reproduce these findings on my CGB.
|
||||||
Todo: Find out why my tests contradict these docs */
|
Todo: Find out why my tests contradict these docs */
|
||||||
if (gb->cgb_mode && !gb->cgb_double_speed &&
|
if (gb->cgb_mode && !gb->cgb_double_speed &&
|
||||||
gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 4) {
|
gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 8) {
|
||||||
uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8};
|
uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8};
|
||||||
if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) {
|
if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) {
|
||||||
gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4;
|
gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4;
|
||||||
@ -709,7 +711,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
|
|
||||||
|
|
||||||
/* Render */
|
/* Render */
|
||||||
int16_t current_lcdc_x = gb->display_cycles % LINE_LENGTH - MODE2_LENGTH - (gb->effective_scx & 0x7) - 7;
|
int16_t current_lcdc_x = (gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) / 2 - (gb->effective_scx & 0x7) - 7;
|
||||||
|
|
||||||
for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) {
|
for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) {
|
||||||
if (gb->previous_lcdc_x >= WIDTH) {
|
if (gb->previous_lcdc_x >= WIDTH) {
|
||||||
|
@ -309,7 +309,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
|
|||||||
}
|
}
|
||||||
gb->turbo = old_turbo;
|
gb->turbo = old_turbo;
|
||||||
gb->turbo_dont_skip = old_dont_skip;
|
gb->turbo_dont_skip = old_dont_skip;
|
||||||
return gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb);
|
return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
|
||||||
}
|
}
|
||||||
|
|
||||||
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
|
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
|
||||||
|
14
Core/gb.h
14
Core/gb.h
@ -267,7 +267,7 @@ struct GB_gameboy_internal_s {
|
|||||||
bool hdma_on;
|
bool hdma_on;
|
||||||
bool hdma_on_hblank;
|
bool hdma_on_hblank;
|
||||||
uint8_t hdma_steps_left;
|
uint8_t hdma_steps_left;
|
||||||
uint16_t hdma_cycles;
|
uint16_t hdma_cycles; // in 8MHz units
|
||||||
uint16_t hdma_current_src, hdma_current_dest;
|
uint16_t hdma_current_src, hdma_current_dest;
|
||||||
|
|
||||||
uint8_t dma_steps_left;
|
uint8_t dma_steps_left;
|
||||||
@ -332,7 +332,7 @@ struct GB_gameboy_internal_s {
|
|||||||
|
|
||||||
/* Timing */
|
/* Timing */
|
||||||
GB_SECTION(timing,
|
GB_SECTION(timing,
|
||||||
uint32_t display_cycles;
|
uint32_t display_cycles; // In 8 MHz units
|
||||||
uint32_t div_cycles;
|
uint32_t div_cycles;
|
||||||
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
|
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
|
||||||
uint16_t serial_cycles;
|
uint16_t serial_cycles;
|
||||||
@ -418,7 +418,7 @@ struct GB_gameboy_internal_s {
|
|||||||
|
|
||||||
/* Timing */
|
/* Timing */
|
||||||
uint64_t last_sync;
|
uint64_t last_sync;
|
||||||
uint64_t cycles_since_last_sync;
|
uint64_t cycles_since_last_sync; // In 8MHz units
|
||||||
|
|
||||||
/* Audio */
|
/* Audio */
|
||||||
GB_apu_output_t apu_output;
|
GB_apu_output_t apu_output;
|
||||||
@ -438,8 +438,8 @@ struct GB_gameboy_internal_s {
|
|||||||
GB_serial_transfer_end_callback_t serial_transfer_end_callback;
|
GB_serial_transfer_end_callback_t serial_transfer_end_callback;
|
||||||
|
|
||||||
/* IR */
|
/* IR */
|
||||||
long cycles_since_ir_change;
|
long cycles_since_ir_change; // In 8MHz units
|
||||||
long cycles_since_input_ir_change;
|
long cycles_since_input_ir_change; // In 8MHz units
|
||||||
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
|
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
|
||||||
size_t ir_queue_length;
|
size_t ir_queue_length;
|
||||||
|
|
||||||
@ -493,7 +493,7 @@ struct GB_gameboy_internal_s {
|
|||||||
uint32_t ram_size; // Different between CGB and DMG
|
uint32_t ram_size; // Different between CGB and DMG
|
||||||
uint8_t boot_rom[0x900];
|
uint8_t boot_rom[0x900];
|
||||||
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
|
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
|
||||||
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run()
|
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
|
||||||
double clock_multiplier;
|
double clock_multiplier;
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -563,7 +563,7 @@ void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const cha
|
|||||||
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
|
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
|
||||||
|
|
||||||
void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
|
void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
|
||||||
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change);
|
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/
|
||||||
|
|
||||||
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
|
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
|
||||||
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
|
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
|
||||||
|
@ -708,8 +708,8 @@ void GB_dma_run(GB_gameboy_t *gb)
|
|||||||
void GB_hdma_run(GB_gameboy_t *gb)
|
void GB_hdma_run(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
if (!gb->hdma_on) return;
|
if (!gb->hdma_on) return;
|
||||||
while (gb->hdma_cycles >= 8) {
|
while (gb->hdma_cycles >= 0x10) {
|
||||||
gb->hdma_cycles -= 8;
|
gb->hdma_cycles -= 0x10;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < 0x10; i++) {
|
for (uint8_t i = 0; i < 0x10; i++) {
|
||||||
GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
|
GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
|
||||||
|
@ -55,9 +55,9 @@ void GB_timing_sync(GB_gameboy_t *gb)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Prevent syncing if not enough time has passed.*/
|
/* Prevent syncing if not enough time has passed.*/
|
||||||
if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
|
if (gb->cycles_since_last_sync < LCDC_PERIOD / 8) return;
|
||||||
|
|
||||||
uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb);
|
uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
|
||||||
int64_t nanoseconds = get_nanoseconds();
|
int64_t nanoseconds = get_nanoseconds();
|
||||||
if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) {
|
if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) {
|
||||||
nsleep(target_nanoseconds + gb->last_sync - nanoseconds);
|
nsleep(target_nanoseconds + gb->last_sync - nanoseconds);
|
||||||
@ -145,7 +145,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
|
|
||||||
gb->debugger_ticks += cycles;
|
gb->debugger_ticks += cycles;
|
||||||
|
|
||||||
cycles >>= gb->cgb_double_speed;
|
cycles <<= !gb->cgb_double_speed;
|
||||||
|
|
||||||
// Not affected by speed boost
|
// Not affected by speed boost
|
||||||
gb->hdma_cycles += cycles;
|
gb->hdma_cycles += cycles;
|
||||||
|
Loading…
Reference in New Issue
Block a user