mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 04:39:17 +08:00
e1cf5b13ef
* [agc] WAIT_REG_MEM suspend/resume, draw packet fixes, new HLE exports, debug cleanup
Rebased onto upstream 79a7437 (par274/sharpemu, rewritten history).
- GpuWaitRegistry: DCBs suspended on unsatisfied WAIT_REG_MEM are re-polled
against guest memory on every submit; fixed 64-bit and standard packet parse
offsets, apply the mask, treat PM4 compare function 0 as "always".
- TryReadSubmittedDrawCount: accept the 5-dword ItDrawIndex2 form emitted by
DcbDrawIndex (count at +4); menu draws were silently discarded before.
- sceAgcDriverSubmitMultiDcbs: reversed ABI (rdi=address array, rsi=dword
sizes, rdx=count).
- VideoOut: vblank events, sceVideoOutGetFlipStatus, buffers registered via
sceVideoOutRegisterBuffers are valid flip targets.
- New HLE: libc stdio (fopen/fread/fseek/ftell/fclose/fgets), Dinkumware
_Getpctype ctype table, NpTrophy2 stubs, AMPR PAK sequential-read tracker,
MsgDialog lifecycle, NGS2 alt NIDs + dummy vtable for handle objects,
guarded memset intrinsic, abort()/strcasecmp null-arg recovery.
- Removed investigation-only code (INT3 breakpoints, qfont/mcpp dumps,
error-candidate printf traces, unconditional debug logs).
First rendered frame: Quake (PPSA01880) presents a 1920x1080 guest frame.
* Implemented a guarded native intrinsic (rep movsb) in DirectExecutionBackend to bypass HLE dispatch overhead, while preserving memory safety checks.
157 lines
5.7 KiB
C#
157 lines
5.7 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Concurrent;
|
|
using System.Text;
|
|
using SharpEmu.HLE;
|
|
|
|
namespace SharpEmu.Libs.Ampr;
|
|
|
|
/// <summary>
|
|
/// sceAmprAprCommandBufferReadFile carries -1 instead of an absolute offset whenever the guest
|
|
/// wants "the next sequential chunk" of a file. For an id Software PACK archive that sequence is
|
|
/// always header -> directory table -> individual archived files, and the guest never tells us
|
|
/// which archived file it wants next - only the byte count it expects, taken from the very
|
|
/// directory entry it already parsed on its side. This tracks a per-fileId read cursor, and once
|
|
/// the directory table has streamed past, parses it so later reads can be matched back to their
|
|
/// real absolute offset by the size the guest asks for.
|
|
/// </summary>
|
|
internal static class PakDirectoryTracker
|
|
{
|
|
private const int DirectoryEntrySize = 64;
|
|
private const int DirectoryEntryNameLength = 56;
|
|
|
|
private static readonly bool _trace =
|
|
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal) ||
|
|
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR_READS"), "1", StringComparison.Ordinal);
|
|
|
|
private sealed class DirectoryEntry(string name, uint filePos, uint fileLen)
|
|
{
|
|
public string Name { get; } = name;
|
|
public uint FilePos { get; } = filePos;
|
|
public uint FileLen { get; } = fileLen;
|
|
public bool Consumed { get; set; }
|
|
}
|
|
|
|
private sealed class FileState
|
|
{
|
|
public ulong NextOffset;
|
|
public bool ExpectingDirectory;
|
|
public List<DirectoryEntry>? Directory;
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<uint, FileState> _state = new();
|
|
|
|
/// <summary>
|
|
/// Resolves what "read the next sequential chunk of size <paramref name="requestedSize"/>"
|
|
/// really means for this fileId: an unconsumed directory entry whose length matches, if the
|
|
/// directory has already been parsed, otherwise the tracked linear cursor.
|
|
/// </summary>
|
|
public static ulong ResolveSequentialOffset(uint fileId, ulong requestedSize)
|
|
{
|
|
var state = _state.GetOrAdd(fileId, static _ => new FileState());
|
|
|
|
if (state.Directory is { } entries)
|
|
{
|
|
foreach (var entry in entries)
|
|
{
|
|
if (!entry.Consumed && entry.FileLen == requestedSize)
|
|
{
|
|
entry.Consumed = true;
|
|
if (_trace)
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][TRACE] pak.directory_match: id=0x{fileId:X8} name='{entry.Name}' " +
|
|
$"filepos=0x{entry.FilePos:X8} filelen=0x{entry.FileLen:X8}");
|
|
}
|
|
|
|
return entry.FilePos;
|
|
}
|
|
}
|
|
}
|
|
|
|
return state.NextOffset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feeds back a completed read so the tracker can advance its cursor, recognize a PACK
|
|
/// header and jump straight to its directory table, or parse the directory table itself
|
|
/// once it streams past.
|
|
/// </summary>
|
|
public static void OnReadCompleted(CpuContext ctx, uint fileId, ulong destination, ulong fileOffset, ulong bytesRead)
|
|
{
|
|
if (bytesRead == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var state = _state.GetOrAdd(fileId, static _ => new FileState());
|
|
|
|
if (state.ExpectingDirectory && fileOffset == state.NextOffset)
|
|
{
|
|
state.Directory = TryParseDirectory(ctx, destination, bytesRead);
|
|
state.ExpectingDirectory = false;
|
|
state.NextOffset = fileOffset + bytesRead;
|
|
if (_trace)
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][TRACE] pak.directory_parsed: id=0x{fileId:X8} entries={state.Directory?.Count ?? 0}");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (fileOffset == 0 && bytesRead >= 12 && TryReadPackHeader(ctx, destination, out var dirOffset))
|
|
{
|
|
state.NextOffset = dirOffset;
|
|
state.ExpectingDirectory = true;
|
|
return;
|
|
}
|
|
|
|
state.NextOffset = fileOffset + bytesRead;
|
|
}
|
|
|
|
private static bool TryReadPackHeader(CpuContext ctx, ulong destination, out ulong dirOffset)
|
|
{
|
|
dirOffset = 0;
|
|
Span<byte> header = stackalloc byte[12];
|
|
if (!ctx.Memory.TryRead(destination, header) ||
|
|
header[0] != (byte)'P' || header[1] != (byte)'A' || header[2] != (byte)'C' || header[3] != (byte)'K')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dirOffset = BinaryPrimitives.ReadUInt32LittleEndian(header[4..8]);
|
|
return true;
|
|
}
|
|
|
|
private static List<DirectoryEntry>? TryParseDirectory(CpuContext ctx, ulong destination, ulong length)
|
|
{
|
|
var count = (int)(length / DirectoryEntrySize);
|
|
if (count <= 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var entries = new List<DirectoryEntry>(count);
|
|
Span<byte> record = stackalloc byte[DirectoryEntrySize];
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
if (!ctx.Memory.TryRead(destination + (ulong)(i * DirectoryEntrySize), record))
|
|
{
|
|
break;
|
|
}
|
|
|
|
var nameBytes = record[..DirectoryEntryNameLength];
|
|
var nullIndex = nameBytes.IndexOf((byte)0);
|
|
var name = Encoding.ASCII.GetString(nullIndex >= 0 ? nameBytes[..nullIndex] : nameBytes);
|
|
var filePos = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(56, 4));
|
|
var fileLen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(60, 4));
|
|
entries.Add(new DirectoryEntry(name, filePos, fileLen));
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
}
|