M2 complete: disk saving works via MSXgl's tool/disk_save module
Replaces the hand-rolled PHYDIO/DSKIO sector writer, which never worked,
with engine/src/tool/disk_save.h — added in MSXgl v1.3.0 for exactly this
("save to disk from a ROM application"). Saves are now a real FAT file,
KISSAT00.SAV, on the disk in drive A.
Four non-obvious requirements, all documented in CLAUDE.md:
- LibModules needs both "tool/disk_save" and "dos"
- ROMDelayBoot = true, or the Disk ROM's INIT never runs
- boot then takes 40-90s of emulated time (use `set throttle off`)
- DiskSave_Check() returns SAVEDATA_UNSIGNED for good files: with APPSIGN
it wants the first 4 bytes to be g_AppSignature, but DiskSave_Save()
writes the payload raw and never adds it
Also fixes a save-timing bug: the write now happens in AdvanceDay() after
the day increments, so the file describes the morning the player wakes to.
Saving during the evening made a reload replay that day and double-count
its cups.
Verified: play a full day, cold boot, resume at day 2 with served intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,83 +62,53 @@ Read `docs/CONVERSATION.md` for the original design discussion and rationale.
|
||||
- Erase blits must repaint the *actual* background at that spot — the counter
|
||||
area has three bands (wainscot / counter top highlight / counter top).
|
||||
|
||||
## Disk saving from a cartridge ROM — status: UNRESOLVED
|
||||
## Disk saving from a cartridge ROM — WORKING
|
||||
|
||||
M2 wants saves on a real `.dsk`. The game is a cartridge MegaROM, which makes
|
||||
this awkward. What is established so far, all verified on
|
||||
`Philips_NMS_8250` + `-carta` + `-diska`:
|
||||
Saves go to a real file on a real disk: `KISSAT00.SAV` in the root of the
|
||||
disk in drive A. The disk stays a normal FAT disk you can inspect and copy.
|
||||
|
||||
- **A cartridge ROM does boot fine on a disk machine.** Not a problem.
|
||||
- **`ROMDelayBoot = true` is required.** A cartridge's INIT normally runs
|
||||
during the BIOS slot scan, and MSXgl never returns from it — so the Disk
|
||||
ROM's own INIT never happens, `NMBDRV` stays 0 and the disk hooks are never
|
||||
patched. This option installs an H.STKE hook and returns to the scan
|
||||
instead. **Boot becomes much slower** — the game appears at ~40-90s of
|
||||
emulated time, so early screenshots will show a blue BASIC screen and look
|
||||
like a hang when they are not.
|
||||
- **Any call into the disk system must be made from RAM.** Both PHYDIO and
|
||||
CALSLT reach the Disk ROM at 4000h — the same page as this cartridge — so
|
||||
the calling instruction is switched out mid-call unless it lives elsewhere.
|
||||
`Disk_InitStub()` copies a small stub to RAM for exactly this. Calling
|
||||
directly from ROM black-screens the machine.
|
||||
- With the above in place, `NMBDRV` reads 2 and `MASTER` is non-zero, so the
|
||||
Disk ROM *has* initialised by the time the game runs.
|
||||
**Use `engine/src/tool/disk_save.h`.** MSXgl v1.3.0 added this module
|
||||
specifically for "save to disk from a ROM application". Do not hand-roll
|
||||
sector I/O — an earlier attempt using PHYDIO (Main ROM 0144h) and a direct
|
||||
CALSLT to DSKIO (4010h) had both entry points return carry-clear *while
|
||||
transferring nothing*, proven with a sentinel that survived the call intact.
|
||||
|
||||
**The remaining blocker:** both `PHYDIO` (Main ROM 0144h) and a direct
|
||||
`CALSLT` to `DSKIO` (Disk ROM 4010h) return **carry clear — "success" — while
|
||||
never touching the destination buffer.** Proven with a 0xAA sentinel that
|
||||
survives the call intact. Page 0 is confirmed to be the Main ROM at call time
|
||||
(0x002D reads 1 = MSX2, 0x0144 holds 0xC3 = `JP`), so the entry points
|
||||
themselves are addressable.
|
||||
Four things are required, none of them obvious:
|
||||
|
||||
Because a clean error code is *not* proof that a read happened, `Disk_Probe()`
|
||||
now verifies the buffer actually changed and treats "no change" as no disk.
|
||||
The game degrades gracefully: no disk means day 1, nothing persists, and the
|
||||
evening card says so. Nothing crashes on either C-BIOS or a real-BIOS machine.
|
||||
1. **`LibModules` must include both `"tool/disk_save"` and `"dos"`.** The
|
||||
module alone will not link. See `projects/samples/s_save.js` for the
|
||||
reference configuration, and `s_save.c` for usage.
|
||||
2. **`ROMDelayBoot = true`.** A cartridge's INIT normally runs during the BIOS
|
||||
slot scan and MSXgl never returns from it, so the Disk ROM's own INIT never
|
||||
happens and there is no disk system to talk to. This option installs an
|
||||
H.STKE hook and returns to the scan instead.
|
||||
3. **Boot becomes slow.** The game appears at roughly 40-90s of *emulated*
|
||||
time. Screenshots before that show a blue BASIC screen and look exactly
|
||||
like a hang. Use `set throttle off` in the test script so this costs
|
||||
seconds of wall clock, not minutes.
|
||||
4. **`DiskSave_Check()` returns `SAVEDATA_UNSIGNED` for perfectly good
|
||||
files.** With `AppSignature = true` (`-DAPPSIGN`), `DiskSave_Check()`
|
||||
requires the file's first four bytes to equal `g_AppSignature` — but
|
||||
`DiskSave_Save()` writes the payload raw and never adds it. Treat
|
||||
`SAVEDATA_UNSIGNED` as success; `SaveState` carries its own magic, version
|
||||
and checksum, which is a stronger check regardless.
|
||||
|
||||
Next things to try: check whether the H.PHYD RAM hook is genuinely patched at
|
||||
that moment; try a different disk machine profile; or reconsider the medium
|
||||
(see PLAN.md §8 item 1).
|
||||
**Save timing:** written in `AdvanceDay()` *after* the day counter increments,
|
||||
so the file always describes the morning the player wakes to. Saving during
|
||||
the evening instead makes a reload replay the day just finished and
|
||||
double-count its cups.
|
||||
|
||||
**RTC CMOS is not a viable fallback**, despite `RTC_USE_SAVEDATA` being TRUE
|
||||
in `msxgl_config.h`. Block 3 of the RP-5C01 is 13 nibbles, and MSXgl's
|
||||
`RTC_SaveData()` (`engine/src/clock.c`) stores exactly **6 bytes** — against a
|
||||
~40-byte `SaveState`. `RTC_SaveDataSigned()` is tighter still. Remaining
|
||||
options are cartridge SRAM, the FM-PAC SRAM (MSXgl has a `PAC` module), or
|
||||
shrinking the save to a 6-byte fingerprint — which would cost per-character
|
||||
affinity and money, so it is a design decision, not just a storage one.
|
||||
Reference: MSX2 Technical Handbook ch.5, CLOCK-IC section.
|
||||
Absent or unusable disk is not an error the player has to handle — it just
|
||||
means nothing persists, and the morning card says so.
|
||||
|
||||
## Architecture conventions
|
||||
- VRAM page 0: visible shop scene bitmap. Page 1: asset warehouse (sprite
|
||||
frames, portraits, UI tiles) blitted with VDP commands (HMMM/HMMV).
|
||||
- Seasons and time-of-day are palette swaps only (32-byte tables); never
|
||||
duplicate background art per season.
|
||||
- Sprite mode 2, two layered 16×16 sprites per customer (outline + fill).
|
||||
- Portraits are 48×48, three expressions per character max.
|
||||
- Game state lives in a single `SaveState` struct (~40 bytes); see DESIGN.md.
|
||||
Implemented in `kissaten.c` with a magic word, version byte and checksum;
|
||||
a failed or absent load is not an error, it just means day one.
|
||||
- The day is a three-phase machine (morning / open / evening) and the clock is
|
||||
**event-driven** — customers are the clock. `CUSTOMERS_PER_DAY` is the
|
||||
single pacing dial.
|
||||
- Dialogue scenes are data, not code: trigger conditions → portrait,
|
||||
expression, text, optional choice, effects.
|
||||
Test:
|
||||
```
|
||||
openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk
|
||||
```
|
||||
Create a blank 720K disk with `tools/build/msxtar/msxtar -cf save.dsk --dos1 --size=720K`.
|
||||
|
||||
## Code style
|
||||
- C99, MSXgl idioms (u8/u16 types, `msxgl_` module prefixes)
|
||||
- Keep ISR/VBlank work minimal; game logic in main loop
|
||||
- Comment bank-switching boundaries explicitly
|
||||
- Tools are Node.js (plain JS, no framework), living in `tools/`
|
||||
|
||||
## Scope ladder (build in this order; every stage must be playable)
|
||||
1. ✅ Shop scene + one customer + brew minigame + serve loop (no story)
|
||||
2. ◐ Day/night cycle + save + money — day counter, three phases and the
|
||||
journal line work; **persistence is blocked** (see the disk section below).
|
||||
Money deliberately deferred to stage 5, since nothing gates on it before.
|
||||
3. Dialogue engine + two regulars with short arcs ← real milestone
|
||||
4. Seasons/weather/palette system
|
||||
5. Remaining cast, upgrades, endings
|
||||
|
||||
`docs/PLAN.md` expands this into ten milestones with per-stage requirements
|
||||
and a decision queue. `docs/SCRIPT.md` holds the full first-draft script.
|
||||
**RTC CMOS is not an alternative** for a save this size, despite
|
||||
`RTC_USE_SAVEDATA` being TRUE in `msxgl_config.h`. Block 3 of the RP-5C01 is
|
||||
13 nibbles and MSXgl's `RTC_SaveData()` stores exactly **6 bytes**, against a
|
||||
~40-byte `SaveState`. (MSXgl has no cartridge-SRAM mapper target either; the
|
||||
`PAC` module's FM-PAC SRAM, 8 x 1024 bytes, is the other real option.)
|
||||
|
||||
@@ -76,10 +76,9 @@ rains" patterns.
|
||||
|
||||
**Produces:** `SaveState` struct, phase state machine, save/load module.
|
||||
|
||||
**Status:** day counter, three phases, journal line and `SaveState` are done
|
||||
and playable. **Persistence is blocked** — sector I/O from a cartridge ROM
|
||||
reports success without transferring data; full findings in the disk section
|
||||
of `../CLAUDE.md`.
|
||||
**Status: ✅ complete.** Day counter, three phases, journal line, `SaveState`
|
||||
and disk persistence all working. Verified by playing a day, cold-booting, and
|
||||
resuming at day 2 with the served count intact.
|
||||
|
||||
**Pacing dial, revisited at the end.** `CUSTOMERS_PER_DAY` in `kissaten.c` is
|
||||
the only number that sets day length. Worth reconsidering at M10 as a
|
||||
@@ -482,7 +481,7 @@ milestone that consumes them.
|
||||
|
||||
| # | Needed for | Decision | My recommendation |
|
||||
|---|---|---|---|
|
||||
| 1 | M2 | ~~Save medium~~ → **save disk (.dsk), asset supplied** | ⚠ decided, but **not working yet** — see the disk section in `../CLAUDE.md`. Sector I/O reports success without transferring. **The RTC CMOS fallback I previously suggested is not viable**: block 3 is 13 nibbles, and MSXgl's `RTC_SaveData()` stores **6 bytes**, against a ~40-byte `SaveState`. Real fallbacks are cartridge SRAM, the FM-PAC SRAM, or shrinking the save to a 6-byte fingerprint |
|
||||
| 1 | M2 | ~~Save medium~~ → **save disk (.dsk)** | ✅ working, via MSXgl's `tool/disk_save` module. Saves `KISSAT00.SAV` to drive A. Four non-obvious requirements — see the disk section in `../CLAUDE.md` |
|
||||
| 2 | M2 | ~~Day length~~ → **5 min / 5 customers** | ✅ done. One constant, `CUSTOMERS_PER_DAY` in `kissaten.c` |
|
||||
| 3 | M2 | ~~Clock model~~ → **event-driven** | ✅ done. Customers are the clock |
|
||||
| 4 | M3 | ~~Who writes the dialogue~~ → **drafted by me, you edit** | ✅ resolved |
|
||||
@@ -510,8 +509,9 @@ project, and both want answering now.
|
||||
`../CLAUDE.md` for everything established so far.
|
||||
2. ✅ **Aki's beats as prose** — done, along with the other three arcs, in
|
||||
`SCRIPT.md`. Awaiting your edit pass.
|
||||
3. ⬜ **Unblock saving.** Either keep digging at the disk path, or switch
|
||||
medium. Note the RTC CMOS fallback is off the table (§8 item 1).
|
||||
3. ⬜ **M3 vertical slice** — Aki's first six beats, hardcoded, with a scratch
|
||||
portrait. The next real milestone, and the one that decides whether the
|
||||
game works (§2, M3).
|
||||
4. ⬜ **Start the portrait question** (§5) — still the only item with external
|
||||
lead time, and now the longest pole by some distance.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,6 +7,7 @@
|
||||
// INCLUDES
|
||||
//=============================================================================
|
||||
#include "msxgl.h"
|
||||
#include "tool/disk_save.h"
|
||||
|
||||
// Fonts data
|
||||
#include "font/font_mgl_sample6.h"
|
||||
@@ -177,8 +178,7 @@ u8 g_SpacePrev = 0;
|
||||
u8 g_Phase = PHASE_MORNING;
|
||||
u8 g_DayServed = 0; // customers served so far today
|
||||
u8 g_DayQuality = 0; // summed brew quality today, for the journal line
|
||||
u8 g_DiskErr = 0; // last disk error code (0 = fine)
|
||||
bool g_HasDisk = FALSE; // a usable save disk was found at boot
|
||||
bool g_SaveOk = TRUE; // did the last write succeed (shown next morning)
|
||||
|
||||
//=============================================================================
|
||||
// SAVE STATE (DESIGN.md §6)
|
||||
@@ -213,134 +213,26 @@ typedef struct
|
||||
|
||||
SaveState g_Save;
|
||||
|
||||
// One 512-byte sector staging buffer. Must live in RAM (page 3) — PHYDIO
|
||||
// writes straight into it, and pages 1-2 are ROM on a cartridge target.
|
||||
u8 g_Sector[512];
|
||||
|
||||
// Sector holding the save, derived from the disk's own BPB at boot rather
|
||||
// than hardcoded, so a 360K disk works as well as the 720K one.
|
||||
u16 g_SaveSector = 14;
|
||||
|
||||
//=============================================================================
|
||||
// DISK ACCESS
|
||||
// DISK SAVE
|
||||
//=============================================================================
|
||||
|
||||
// Number of disk drives, maintained by the Disk ROM at boot. Zero means there
|
||||
// is no disk interface at all — and on such a machine (C-BIOS, for one) the
|
||||
// PHYDIO hook is not patched and calling it is not safe.
|
||||
#define M_NMBDRV_ADDR 0xF347
|
||||
#define M_MASTER_ADDR 0xF348 // main Disk ROM slot, 0 if there is none
|
||||
|
||||
// PHYDIO trampoline, executed from RAM.
|
||||
//
|
||||
// PHYDIO (Main-ROM 0144h) reaches the Disk ROM through an inter-slot call, and
|
||||
// the Disk ROM lives at 4000h — the same page as this cartridge. For the
|
||||
// duration of the call our ROM is switched out of page 1, so the instruction
|
||||
// making the call must not itself be in page 1. Copying these twelve bytes to
|
||||
// RAM and calling *them* keeps the call site alive across the switch.
|
||||
u8 g_DiskStub[21];
|
||||
|
||||
void Disk_InitStub()
|
||||
{
|
||||
// Calls DSKIO (Disk ROM 4010h) through CALSLT rather than the Main ROM's
|
||||
// PHYDIO wrapper at 0144h: PHYDIO jumps to a RAM hook that this machine
|
||||
// leaves unpatched, so it returns carry-clear — reporting success while
|
||||
// never touching the buffer. Going straight at the Disk ROM avoids it.
|
||||
// Uses MSXgl's tool/disk_save module (added in v1.3.0, "save to disk from a
|
||||
// ROM application"). It writes a real named file — KISSAT0.SAV — through the
|
||||
// Disk ROM, so the save disk stays a normal, inspectable FAT disk.
|
||||
//
|
||||
// IY high byte must hold the slot ID, and MASTER lives at F348h, so a
|
||||
// word load from F347h lands it in IYh exactly as CALSLT wants.
|
||||
static const u8 code[21] =
|
||||
{
|
||||
0xF3, // di
|
||||
0xFD, 0x2A, 0x47, 0xF3, // ld iy, (0xF347) ; IYh = MASTER
|
||||
0xDD, 0x21, 0x10, 0x40, // ld ix, #0x4010 ; DSKIO
|
||||
0xCD, 0x1C, 0x00, // call 0x001C ; CALSLT
|
||||
0x38, 0x04, // jr c, err
|
||||
0x2E, 0x00, // ld l, #0 ; success
|
||||
0xFB, // ei
|
||||
0xC9, // ret
|
||||
0x6F, // err: ld l, a ; BIOS error code
|
||||
0xFB, // ei
|
||||
0xC9, // ret
|
||||
};
|
||||
Mem_Copy(code, g_DiskStub, sizeof(code));
|
||||
}
|
||||
// This replaced a hand-rolled PHYDIO/DSKIO sector writer that never worked:
|
||||
// both entry points returned carry-clear without transferring anything. Do
|
||||
// not go back to raw sectors; use this module.
|
||||
//
|
||||
// Requirements, both non-obvious:
|
||||
// - LibModules must include BOTH "tool/disk_save" and "dos".
|
||||
// - ROMDelayBoot = true, or the Disk ROM's INIT never runs (see CLAUDE.md).
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Absolute sector read/write into g_Sector.
|
||||
// carry clear = read, carry set = write
|
||||
// A=drive B=sector count C=media ID DE=first sector HL=buffer
|
||||
// Returns the BIOS error code, or 0 on success.
|
||||
u8 Disk_Access(u16 sector, u8 write) __NAKED
|
||||
{
|
||||
sector; // on stack
|
||||
write;
|
||||
// Save slot. Multiple save files would be entries 1, 2, ... on the same disk.
|
||||
#define SAVE_ENTRY 0
|
||||
|
||||
__asm
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
|
||||
ld e, 4(ix) // sector low
|
||||
ld d, 5(ix) // sector high
|
||||
ld a, 6(ix) // write flag
|
||||
|
||||
ld hl, #_g_Sector
|
||||
ld b, #1 // one sector
|
||||
ld c, #0xF9 // media ID: 720K DS/DD
|
||||
or a // test flag; also clears carry (= read)
|
||||
jr z, phydio_go
|
||||
scf // carry set = write
|
||||
phydio_go:
|
||||
ld a, #0 // drive A:. 'ld a,n' preserves flags
|
||||
call _g_DiskStub // -> PHYDIO, from RAM. Returns code in L
|
||||
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Read the BIOS Parameter Block and work out where the data area starts.
|
||||
// Returns FALSE if no disk responded at all.
|
||||
bool Disk_Probe()
|
||||
{
|
||||
u8 drives = *(const u8*)M_NMBDRV_ADDR;
|
||||
|
||||
// Both of these are set by the Disk ROM's INIT. Without them the entry
|
||||
// points below are not safe to call — on C-BIOS this work area is never
|
||||
// initialised at all, and calling anyway black-screens the machine.
|
||||
if (drives == 0 || drives > 8)
|
||||
return FALSE;
|
||||
if (*(const u8*)M_MASTER_ADDR == 0)
|
||||
return FALSE;
|
||||
|
||||
Disk_InitStub();
|
||||
|
||||
// Sentinel: the disk entry points can return carry-clear ("success")
|
||||
// without having touched the buffer, so a clean error code is not on its
|
||||
// own proof that a read happened. Verify the buffer actually changed.
|
||||
Mem_Set(0xAA, g_Sector, 16);
|
||||
g_DiskErr = Disk_Access(0, 0);
|
||||
if (g_DiskErr != 0)
|
||||
return FALSE;
|
||||
if (g_Sector[0] == 0xAA && g_Sector[11] == 0xAA)
|
||||
return FALSE; // nothing was read; treat the disk as unusable
|
||||
|
||||
u16 bytesPerSec = g_Sector[11] | ((u16)g_Sector[12] << 8);
|
||||
u16 reserved = g_Sector[14] | ((u16)g_Sector[15] << 8);
|
||||
u8 numFATs = g_Sector[16];
|
||||
u16 rootEntries = g_Sector[17] | ((u16)g_Sector[18] << 8);
|
||||
u16 secPerFAT = g_Sector[22] | ((u16)g_Sector[23] << 8);
|
||||
|
||||
if (bytesPerSec != 512) // anything else is not a disk we made
|
||||
return FALSE;
|
||||
|
||||
// Root directory occupies ceil(entries * 32 / 512) sectors
|
||||
u16 rootSecs = (u16)((rootEntries + 15) >> 4);
|
||||
g_SaveSector = reserved + (u16)numFATs * secPerFAT + rootSecs;
|
||||
return TRUE;
|
||||
}
|
||||
bool g_HasDisk = FALSE; // a usable save disk was found at boot
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
u8 Save_Checksum(const SaveState* s)
|
||||
@@ -352,25 +244,42 @@ u8 Save_Checksum(const SaveState* s)
|
||||
return (u8)(0 - sum);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// A missing drive or a blank disk is not an error the player has to deal
|
||||
// with — it just means nothing will persist.
|
||||
void Disk_Init()
|
||||
{
|
||||
DiskSave_SetName("KISSAT"); // 6 chars max
|
||||
DiskSave_SetExtension("SAV"); // 3 chars max
|
||||
g_HasDisk = (DiskSave_Initialize() == SAVEDATA_VALID);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// TRUE if a valid save was loaded; FALSE means "start a new game", which is
|
||||
// also what a blank disk gives us — no error path for the player to hit.
|
||||
// also what a blank disk gives us.
|
||||
bool Save_Load()
|
||||
{
|
||||
if (!g_HasDisk)
|
||||
return FALSE;
|
||||
|
||||
g_DiskErr = Disk_Access(g_SaveSector, 0);
|
||||
if (g_DiskErr != 0)
|
||||
// SAVEDATA_UNSIGNED is expected, not an error: with AppSignature on,
|
||||
// DiskSave_Check() wants the file's first 4 bytes to be g_AppSignature,
|
||||
// but DiskSave_Save() writes the payload raw and never adds it — so a
|
||||
// perfectly good file always reports UNSIGNED. Only a genuinely absent or
|
||||
// unreadable file is a reason to bail; the magic and checksum below are a
|
||||
// stronger check than the signature would have been anyway.
|
||||
u8 status = DiskSave_Check(SAVE_ENTRY);
|
||||
if ((status != SAVEDATA_VALID) && (status != SAVEDATA_UNSIGNED))
|
||||
return FALSE;
|
||||
|
||||
SaveState* s = (SaveState*)g_Sector;
|
||||
if (s->magic != SAVE_MAGIC || s->version != SAVE_VERSION)
|
||||
return FALSE;
|
||||
if (s->checksum != Save_Checksum(s))
|
||||
if (!DiskSave_Load(SAVE_ENTRY, (u8*)&g_Save, sizeof(SaveState)))
|
||||
return FALSE;
|
||||
|
||||
Mem_Copy(s, &g_Save, sizeof(SaveState));
|
||||
// Trust nothing that came off a disk
|
||||
if (g_Save.magic != SAVE_MAGIC || g_Save.version != SAVE_VERSION)
|
||||
return FALSE;
|
||||
if (g_Save.checksum != Save_Checksum(&g_Save))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -384,11 +293,7 @@ bool Save_Write()
|
||||
g_Save.version = SAVE_VERSION;
|
||||
g_Save.checksum = Save_Checksum(&g_Save);
|
||||
|
||||
Mem_Set(0, g_Sector, sizeof(g_Sector));
|
||||
Mem_Copy(&g_Save, g_Sector, sizeof(SaveState));
|
||||
|
||||
g_DiskErr = Disk_Access(g_SaveSector, 1);
|
||||
return (g_DiskErr == 0);
|
||||
return DiskSave_Save(SAVE_ENTRY, (const u8*)&g_Save, sizeof(SaveState));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -605,6 +510,10 @@ void EnterMorning()
|
||||
Dlg_Clear();
|
||||
Dlg_Line(0, "Morning. The shutters go");
|
||||
Dlg_Line(1, "up, the kettle goes on.");
|
||||
if (!g_HasDisk)
|
||||
Dlg_Line(2, "(no save disk in drive A)");
|
||||
else if (!g_SaveOk)
|
||||
Dlg_Line(2, "(save failed)");
|
||||
Dlg_Prompt("[SPACE] open the shop");
|
||||
}
|
||||
|
||||
@@ -630,18 +539,17 @@ void EnterEvening()
|
||||
else
|
||||
Dlg_Line(1, "Tomorrow, a steadier hand.");
|
||||
|
||||
if (Save_Write())
|
||||
Dlg_Prompt("[SPACE] sleep (saved)");
|
||||
else if (!g_HasDisk)
|
||||
Dlg_Prompt("[SPACE] sleep (no disk)");
|
||||
else
|
||||
Dlg_Prompt("[SPACE] sleep (SAVE FAILED)");
|
||||
Dlg_Prompt("[SPACE] sleep");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Save *after* the day rolls over, so the file always describes the morning
|
||||
// the player will wake up to. Saving during the evening instead would make a
|
||||
// reload replay the day just finished and double-count its cups.
|
||||
void AdvanceDay()
|
||||
{
|
||||
g_Save.day++;
|
||||
g_SaveOk = Save_Write();
|
||||
EnterMorning();
|
||||
}
|
||||
|
||||
@@ -861,13 +769,10 @@ void main()
|
||||
g_Frame = 0;
|
||||
g_SpaceHit = FALSE;
|
||||
g_SpacePrev = 1; // treat SPACE as held at boot: no phantom first edge
|
||||
g_DiskErr = 0;
|
||||
g_SaveSector = 14;
|
||||
|
||||
// Save disk. A missing or blank disk is not an error the player has to
|
||||
// deal with — it just means today is day one and nothing will persist.
|
||||
Save_Reset();
|
||||
g_HasDisk = Disk_Probe();
|
||||
Disk_Init();
|
||||
Save_Load();
|
||||
|
||||
EnterMorning();
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
// ASCII-safe name: used for output filenames (ROM, symbols, intermediates)
|
||||
ProjName = "kissaten";
|
||||
ProjModules = [ ProjName ];
|
||||
LibModules = [ "system", "bios", "vdp", "print", "input", "memory", "math" ];
|
||||
// "tool/disk_save" needs "dos" alongside it — see projects/samples/s_save.js
|
||||
LibModules = [ "system", "bios", "vdp", "print", "input", "memory", "math",
|
||||
"tool/disk_save", "dos" ];
|
||||
|
||||
Machine = "2";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user