diff --git a/CMakeLists.txt b/CMakeLists.txt index 39b7b1fc4..1c02efeac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,8 @@ set(Impacto_Src src/games/cclcc/delusiontrigger.cpp src/games/cclcc/yesnotrigger.cpp src/games/cclcc/savesystem.cpp + src/games/cclcc/savesystem_ps4.cpp + src/games/cclcc/savesystem_switch.cpp src/games/cclcc/systemmenu.cpp src/games/cclcc/helpmenu.cpp @@ -390,6 +392,7 @@ set(Impacto_Src src/audio/atrac9audiostream.cpp src/audio/adxaudiostream.cpp src/audio/hcaaudiostream.cpp + src/audio/lopusaudiostream.cpp src/video/videosystem.cpp src/video/videoplayer.cpp @@ -744,7 +747,17 @@ set(Impacto_Header src/vm/inst_misc.h src/vm/inst_movie.h src/vm/inst_sound.h + src/vm/opcodetables_cc.h + src/vm/opcodetables_chlcc.h + src/vm/opcodetables_chn.h + src/vm/opcodetables_darling.h + src/vm/opcodetables_dash.h + src/vm/opcodetables_lccswitch.h + src/vm/opcodetables_mo6tw.h + src/vm/opcodetables_mo7.h + src/vm/opcodetables_mo8.h src/vm/opcodetables_rne.h + src/vm/opcodetables_sgps3.h src/vm/interface/scene3d.h src/vm/interface/input.h @@ -760,6 +773,7 @@ set(Impacto_Header src/audio/atrac9audiostream.h src/audio/adxaudiostream.h src/audio/hcaaudiostream.h + src/audio/lopusaudiostream.h src/video/videosystem.h src/video/videoplayer.h @@ -997,6 +1011,7 @@ else () ) find_package(Ogg CONFIG REQUIRED) find_package(Vorbis CONFIG REQUIRED) + find_package(Opus CONFIG REQUIRED) find_package(WebP CONFIG REQUIRED) list(APPEND Impacto_Libs @@ -1006,6 +1021,7 @@ else () Ogg::ogg Vorbis::vorbis Vorbis::vorbisfile + Opus::opus ) if (NX) diff --git a/gamedefinitions.lua b/gamedefinitions.lua index f80dacb79..bdc41d1e4 100644 --- a/gamedefinitions.lua +++ b/gamedefinitions.lua @@ -9,7 +9,7 @@ root.GameDefinitions = { } }, cclcc = { - Name = "CHAOS;CHILD Love Chu☆Chu!!", + Name = "CHAOS;CHILD Love Chu☆Chu!! (PS4)", LauncherOrderId = 3, LauncherTheme = 0xFF7698, GameProfile = root.BasePaths.RootProfilesDir .. "/cclcc/game.lua", @@ -17,10 +17,19 @@ root.GameDefinitions = { English = root.BasePaths.RootPatchesDir .. "/english/profiles/cclcc/patch.lua", } }, + ["cclcc-switch"] = { + Name = "CHAOS;CHILD Love Chu☆Chu!! (Switch)", + LauncherOrderId = 4, + LauncherTheme = 0xFF7698, + GameProfile = root.BasePaths.RootProfilesDir .. "/cclcc-switch/game.lua", + Patch = { + English = root.BasePaths.RootPatchesDir .. "/english/profiles/cclcc-switch/patch.lua", + } + }, mo6tw = { Hidden = true, Name = "Memories Off 6 ~T-Wave~", - LauncherOrderId = 5, + LauncherOrderId = 6, GameProfile = root.BasePaths.RootProfilesDir .. "/mo6tw/game.lua", }, darling = { @@ -32,7 +41,7 @@ root.GameDefinitions = { dash = { Hidden = true, Name = "ROBOTICS;NOTES DaSH", - LauncherOrderId = 4, + LauncherOrderId = 5, GameProfile = root.BasePaths.RootProfilesDir .. "/dash/game.lua", }, rne = { @@ -44,19 +53,19 @@ root.GameDefinitions = { characterviewer = { Hidden = true, Name = "Character Viewer", - LauncherOrderId = 6, + LauncherOrderId = 7, GameProfile = root.BasePaths.RootProfilesDir .. "/characterviewer/game.lua", }, modelviewer = { Hidden = true, Name = "Model Viewer", - LauncherOrderId = 7, + LauncherOrderId = 8, GameProfile = root.BasePaths.RootProfilesDir .. "/modelviewer/game.lua", }, ["modelviewer-dash"] = { Hidden = true, Name = "Model Viewer DaSH", - LauncherOrderId = 8, + LauncherOrderId = 9, GameProfile = root.BasePaths.RootProfilesDir .. "/modelviewer-dash/game.lua", }, }; \ No newline at end of file diff --git a/profiles/cc/hud/backlogmenu.lua b/profiles/cc/hud/backlogmenu.lua index 1c0fc6b35..91ee00996 100644 --- a/profiles/cc/hud/backlogmenu.lua +++ b/profiles/cc/hud/backlogmenu.lua @@ -50,12 +50,6 @@ root.Sprites["BacklogHeader"] = { Bounds = { X = 962, Y = 1210, Width = 542, Height = 542 }, }; -root.SpriteSheets["BacklogMask"] = { - Path = { Mount = "system", Id = 2 }, - DesignWidth = 1920, - DesignHeight = 1080 -}; - root.Sprites["BacklogControls"] = { Sheet = "Backlog", Bounds = { X = 0, Y = 1153, Width = 1920, Height = 57 }, diff --git a/profiles/cc/sprites.lua b/profiles/cc/sprites.lua index abffebd59..237c7ff44 100644 --- a/profiles/cc/sprites.lua +++ b/profiles/cc/sprites.lua @@ -1,4 +1,9 @@ root.SpriteSheets = { + ["BacklogMask"] = { + Path = { Mount = "system", Id = 2 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, ["Data"] = { Path = { Mount = "system", Id = 5 }, DesignWidth = 3072, diff --git a/profiles/cclcc-switch/bgeff.lua b/profiles/cclcc-switch/bgeff.lua new file mode 100644 index 000000000..d4027eb3c --- /dev/null +++ b/profiles/cclcc-switch/bgeff.lua @@ -0,0 +1,4 @@ +root.BgEffData = { + BgChaEffectType = BgEffTypeEnum.Disabled; + BgFrameEffectType = BgEffTypeEnum.Disabled; +}; \ No newline at end of file diff --git a/profiles/cclcc-switch/dialogue.lua b/profiles/cclcc-switch/dialogue.lua new file mode 100644 index 000000000..7542cdf41 --- /dev/null +++ b/profiles/cclcc-switch/dialogue.lua @@ -0,0 +1,3 @@ +root.Dialogue.DialogueFont = "Default"; +root.Dialogue.TipsMessageModeIdx = 4; +root.Dialogue.REVMessageModeIdx = 8; \ No newline at end of file diff --git a/profiles/cclcc-switch/font.lua b/profiles/cclcc-switch/font.lua new file mode 100644 index 000000000..1147e4c1a --- /dev/null +++ b/profiles/cclcc-switch/font.lua @@ -0,0 +1,23 @@ +root.Fonts = { + ["Default"] = { + Type = FontType.EdgeDetectedSingleVariableWidthSheet, + Sheet = "Font", + BinaryPath = {Mount = "system", Id = 7}, + BitmapEmWidth = 53, + BitmapEmHeight = 53, + DifferenceFactor = 0.8, + IntensityShift = 0.48, + AlphaShift = 0.36 + }, + + ["Default2"] = { + Type = FontType.EdgeDetectedSingleVariableWidthSheet, + Sheet = "Font", + BinaryPath = {Mount = "system", Id = 9}, + BitmapEmWidth = 53, + BitmapEmHeight = 53, + DifferenceFactor = 0.8, + IntensityShift = 0.48, + AlphaShift = 0.36 + }, +}; \ No newline at end of file diff --git a/profiles/cclcc-switch/game.lua b/profiles/cclcc-switch/game.lua new file mode 100644 index 000000000..60d919fdb --- /dev/null +++ b/profiles/cclcc-switch/game.lua @@ -0,0 +1,96 @@ + +root.ActiveRenderer = RendererType.OpenGL; + +root.LayerCount = 100; +root.GameFeatures = GameFeature.Sc3VirtualMachine | + GameFeature.Renderer2D | + GameFeature.Input | + GameFeature.Audio | + GameFeature.Video | + GameFeature.Subtitles | + GameFeature.DebugMenu | + GameFeature.Achievements | + GameFeature.Overlay; +root.DesignWidth = 1920; +root.DesignHeight = 1080; + +root.WindowName = "CHAOS;CHILD Love Chu☆Chu!!"; +root.WindowIconPath = "resources/cclcc/icondata/icon.png"; +root.CursorArrowPath = "resources/cclcc/icondata/cursor_arrow.png"; +root.CursorPointerPath = "resources/cclcc/icondata/cursor_pointer.png"; + +root.CharaIsMvl = true; +root.UseMoviePriority = true; +root.UseBgChaEffects = false; +root.UseBgFrameEffects = false; +root.UseWaveEffects = true; + +root.ScreenCaptureCount = 2; + +root.Vm = { + StartScript = 2, + StartScriptBuffer = 0, + GameInstructionSet = InstructionSet.LCCSwitch, + + UseMsbStrings = true; + UseReturnIds = true; + UseSeparateMsbArchive = true; + StringEncodingType = StringUnitEncoding.Uint32, + StringIdSize = 4, + + ScrWorkChaStructSize = 24, + ScrWorkChaOffsetStructSize = 10, + ScrWorkBgStructSize = 24, + ScrWorkBgOffsetStructSize = 10, + ScrWorkCaptureStructSize = 20, + ScrWorkCaptureOffsetStructSize = 10, + ScrWorkCaptureEffectInfoStructSize = 3, + ScrWorkBgEffStructSize = 30, + ScrWorkBgEffOffsetStructSize = 18, + ScrWorkMesStructSize = 7, + + MaxLinkedBgBuffers = 2 +}; + +root.PlatformId = 0x100000; -- switch, important for scripts + +include(root.BasePaths.RootProfilesDir .. '/common/animation.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/achievementnotification.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/charset.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/scriptinput.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/scriptvars.lua'); + +include(root.BasePaths.RootProfilesDir .. '/cclcc/scriptinput.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/scriptvars.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/scriptvars.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/sprites.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/savedata.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/savedata.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/tipssystem.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/vfs.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/charset.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/gamespecific.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/font.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/dialogue.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/dialogue.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/configsystem.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/bgeff.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/waveeffects.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/saveicon.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/loadingdisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/datedisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/titlemenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/backlogmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/sysmesboxdisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/selectiondisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/optionsmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/tipsmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/tipsmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/extramenus.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/extramenus.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/tipsnotification.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/tipsnotification.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/systemmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/hud/savemenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/helpmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc-switch/hud/titlemenu.lua'); diff --git a/profiles/cclcc-switch/hud/extramenus.lua b/profiles/cclcc-switch/hud/extramenus.lua new file mode 100644 index 000000000..0c1d9dad9 --- /dev/null +++ b/profiles/cclcc-switch/hud/extramenus.lua @@ -0,0 +1,3 @@ +root.ExtraMenus.LibraryMenu.MusicStringLockedIndex = 3; +root.ExtraMenus.LibraryMenu.LibraryMenuId = 16; +root.ExtraMenus.LibraryMenu.MovieDiskPlayIds = {55, 54, 53, 52}; \ No newline at end of file diff --git a/profiles/cclcc-switch/hud/helpmenu.lua b/profiles/cclcc-switch/hud/helpmenu.lua new file mode 100644 index 000000000..0d642e64d --- /dev/null +++ b/profiles/cclcc-switch/hud/helpmenu.lua @@ -0,0 +1,15 @@ +root.HelpMenu = { + DrawType = DrawComponentType.SystemMenu, + Type = HelpMenuType.CCLCC, + FadeInDuration = 32/60, + FadeOutDuration = 32/60, + NextPageInDuration = 0.4, + NextPageOutDuration = 0.4, + ManualPages = {}, +} + +root.Sprites["ManualPage0"] ={ + Sheet = "Help", + Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 } +}; +root.HelpMenu.ManualPages[#root.HelpMenu.ManualPages + 1] = "ManualPage0"; diff --git a/profiles/cclcc-switch/hud/saveicon.lua b/profiles/cclcc-switch/hud/saveicon.lua new file mode 100644 index 000000000..f807c5de0 --- /dev/null +++ b/profiles/cclcc-switch/hud/saveicon.lua @@ -0,0 +1,31 @@ +root.SaveIcon = { + ForegroundAnimation = "SaveIcon", + DefaultPosition = { X = 104, Y = 736 }, + BackgroundSprite = "SaveIconBg", + BackgroundOffset = { X = 0, Y = 0 }, + BackgroundMaxAlpha = 0.0, + FadeInDuration = 16/60, + FadeOutDuration = 12/60, + FullyVisibleSpriteIndex = 1 +}; + +MakeAnimation({ + Name = "SaveIcon", + Sheet = "Data", + FirstFrameX = 127, + FirstFrameY = 417, + FrameWidth = 179, + ColWidth = 179, + FrameHeight = 62, + RowHeight = 62, + Frames = 2, + Duration = 2*18/60, -- 18 frames per sprite + Rows = 2, + Columns = 1, + PrimaryDirection = AnimationDirections.Down +}); + +root.Sprites["SaveIconBg"] = { + Sheet = "Data", + Bounds = { X = 127, Y = 479, Width = 179, Height = 62 } +}; \ No newline at end of file diff --git a/profiles/cclcc-switch/hud/tipsmenu.lua b/profiles/cclcc-switch/hud/tipsmenu.lua new file mode 100644 index 000000000..94f0e026c --- /dev/null +++ b/profiles/cclcc-switch/hud/tipsmenu.lua @@ -0,0 +1,6 @@ +root.TipsMenu.TipsTextTableIndex = 2; +root.TipsMenu.TipsTextEntryLockedIndex = 7; +root.TipsMenu.TipsTextSortStringIndex = 10; +root.TipsMenu.TipsEntryNameOffset = {X = 100, Y = 10}; +root.TipsMenu.NumberPos = { X = 1190 * 1.5, Y = 52 * 1.5 }; +root.TipsMenu.NumberFontSize = 52; \ No newline at end of file diff --git a/profiles/cclcc-switch/hud/tipsnotification.lua b/profiles/cclcc-switch/hud/tipsnotification.lua new file mode 100644 index 000000000..045d50e9b --- /dev/null +++ b/profiles/cclcc-switch/hud/tipsnotification.lua @@ -0,0 +1,2 @@ +root.TipsNotification.NotificationTextPart1MessageId = 21; +root.TipsNotification.NotificationTextPart2MessageId = 22; \ No newline at end of file diff --git a/profiles/cclcc-switch/hud/titlemenu.lua b/profiles/cclcc-switch/hud/titlemenu.lua new file mode 100644 index 000000000..dc8f4a924 --- /dev/null +++ b/profiles/cclcc-switch/hud/titlemenu.lua @@ -0,0 +1,9 @@ +root.TitleMenu.TitleAnimationFileId = 36; +root.TitleMenu.NewButtonId = 0; +root.TitleMenu.LoadButtonId = 2; +root.TitleMenu.QLoadButtonId = 3; +root.TitleMenu.TipsButtonId = 9; +root.TitleMenu.LibraryButtonId = 5; +root.TitleMenu.EndingListButtonId = 8; +root.TitleMenu.ConfigButtonId = 10; +root.TitleMenu.HelpButtonId = 11; \ No newline at end of file diff --git a/profiles/cclcc-switch/savedata.lua b/profiles/cclcc-switch/savedata.lua new file mode 100644 index 000000000..7628cf383 --- /dev/null +++ b/profiles/cclcc-switch/savedata.lua @@ -0,0 +1,71 @@ +root.SaveData.SaveFilePath = root.BasePaths.RootSavesDir .. "/cclcc-switch/jpn/SAVEDATA_CC.DAT"; +root.SaveData.SystemDataPath = root.BasePaths.RootSavesDir .. "/cclcc-switch/jpn/SYSTEMDATA_CC.DAT" +root.SaveData.QuickDataPath = root.BasePaths.RootSavesDir .. "/cclcc-switch/jpn/MDATA_CC.DAT" +root.SaveData.Type = SaveDataType.CCLCC_SWITCH; +root.SaveData.AlbumEvData = { + {0x5a}, + {0x5b, 0x5c}, + {0xb9}, + {0xba}, + {0x5d, 0x5e, 0x5f}, + {0xbb}, + {0xbc}, + {0xbd}, + {0xbe, 0xbf}, + {0xc0}, + {0xc2, 0x6a}, + {0x6b}, + {0x6c}, + {0x6d, 0x6e}, + {0x6f}, + {0x72}, + {0x73, 0x74}, + {0x75}, + {0x70, 0x71}, + {0x76}, + {0x77, 0x78}, + {0x81}, + {0xc1}, + {0x84}, + {0x83}, + {0x82}, + {0x85, 0x86, 0x87}, + {0x88}, + {0x8b, 0x8c}, + {0x8d, 0x8e, 0x8f, 0x90}, + {0x89, 0x8a}, + {0x91, 0x92, 0x93}, + {0x79, 0x7a, 0x7b, 0x7c}, + {0x94, 0x95}, + {0x96}, + {0x9e, 0x9f}, + {0x98}, + {0x99, 0x9a, 0x9b, 0x9c}, + {0x9d}, + {0x97}, + {0xa4, 0xa5}, + {0xa6}, + {0xa7}, + {0xa0, 0xa1, 0xa2, 0xa3}, + {0xa8}, + {0xa9, 0xaa}, + {0xab}, + {0xac}, + {0xad}, + {0xae, 0xaf}, + {0xb0, 0xb1}, + {0xb2, 0xb3}, + {0xb4}, + {0xb5}, + {0xb6, 0xb7}, + {0xb8}, + {0xb9}, + {0xba}, + {0xbb}, + {0xbc}, + {0xbd, 0xbe}, + {0xbf, 0xc0, 0xc1, 0xc2}, + {0xc3, 0xc4, 0xc5}, + {0xc6}, + {0xc7}, + }; \ No newline at end of file diff --git a/profiles/cclcc-switch/scriptvars.lua b/profiles/cclcc-switch/scriptvars.lua new file mode 100644 index 000000000..b2f84e4b7 --- /dev/null +++ b/profiles/cclcc-switch/scriptvars.lua @@ -0,0 +1,313 @@ +local sv = root.ScriptVars; + +-- W: 1248 -> 2055 +-- W: 1810 -> 2610 +-- W: 3726 -> 3387 - saveTitleNo +-- W: 3361 -> 3324 +-- W: 3748 -> 3445 +-- W: 3749 -> 3446 +-- W: 4561 -> 4593 +-- W: 4586 -> 4554 + +-- F: 1883 -> 2663 +-- F: 1208 -> 2007 +-- F: 1209 -> 2008 +-- F: 1262 -> 2068 +-- F: 1804 -> 2604 +-- F: 1841 -> 2636 +-- F: 1842 -> 2637 + +-- W 2100 same +-- W 2068 same + +-- BG structs and offsets are the same + +sv.SW_ATCHAN_SCROLL_MAX = 1404; +sv.SW_LINEID = 2199; +sv.SW_SCRIPTID= 2200; +sv.SW_AUTOSAVERESTART = 2113; +sv.SW_GAMESTATE = 2114; +sv.SW_TITLEMODE = 2116; +sv.SW_TITLEMOVIECT = 2117; +sv.SW_TITLEDISPCT = 2118; +sv.SW_SYSSEL = 2126; +sv.SW_TITLECUR1 = 2137; +sv.SW_SYSMENUCT = 2139; +sv.SW_SYSMENUALPHA = 2140; +sv.SW_SYSSUBMENUCT = 2144; +sv.SW_SYSSUBMENUNO = 2145; +sv.SW_SYSSUBMENUALPHA = 2146; +sv.SW_MOVIEMODE_CUR = 2174; +sv.SW_CLRALPHA = 2148; +sv.SW_CAP1POSX_OFS = 2480; +sv.SW_CAP1POSY_OFS = 2481; +sv.SW_CAP1SX_OFS = 2482; +sv.SW_CAP1SY_OFS = 2483; +sv.SW_CAP1SIZE_OFS = 2484; +sv.SW_CAP1LX_OFS = 2485; +sv.SW_CAP1LY_OFS = 2486; +sv.SW_CAP1ROTZ_OFS = 2487; +sv.SW_CAP1ALPHA_OFS = 2488; + +sv.SW_BGEFF1_OFSX=2580; +sv.SW_BGEFF1_OFSY=2581; +sv.SW_BGEFF1_SIZE_OFS=2582; +sv.SW_BGEFF1_ROTX_OFS=2583; +sv.SW_BGEFF1_ROTY_OFS=2584; +sv.SW_BGEFF1_ROTZ_OFS=2585; +sv.SW_BGEFF1_ALPHA_OFS=2586; +sv.SW_BGEFF1_SX_OFS=2587; +sv.SW_BGEFF1_SY_OFS=2588; +sv.SW_BGEFF1_MASK_VERTEX1_OFSX=2589; +sv.SW_BGEFF1_MASK_VERTEX1_OFSY=2590; + +sv.SW_MESWIN0POSX_OFS = 2772; +sv.SW_MESWIN0POSY_OFS = 2773; +sv.SW_MASK1ALPHA_OFS = 2778; +sv.SW_MASK2ALPHA_OFS = 2779; +sv.SW_MASK3ALPHA_OFS = 2780; + +sv.SW_MAINTHDP = 3303; +sv.SW_RESTARTMASK = 3316; +sv.SW_PLATFORM = 3317; +sv.SW_SAVEERRORCODE = 3330; +sv.SW_SYSMENUCNO = 3332; +sv.SW_SYSMESALPHA = 3354; +sv.SW_SINSTALL_ALL = 3357; +sv.SW_MENUCT = 3372; +sv.SW_SAVEFILESTATUS = 3382; +sv.SW_SAVEFILENO = 3383; +sv.SW_SAVEFILETYPE = 3384; +sv.SW_SVBGMNO = 3396; +sv.SW_SVBGM2NO = 3397; +sv.SW_SVSENO = 3401; +sv.SW_SVSCRNO1 = 3405; +sv.SW_SVSCRNO2 = 3406; +sv.SW_SVSCRNO3 = 3407; +sv.SW_SVSCRNO4 = 3408; +sv.SW_SYSMENUCTMAX = 3478; -- new var +sv.SW_SYSSUBMENUCTMAX = 3479; -- new var +sv.SW_BG1SURF = 3499; +sv.SW_SVBGNO1 = 3562; +sv.SW_SVCHANO1 = 3570; + +sv.SW_SELNO = 4313; +sv.SW_BGMREQNO = 4315; +sv.SW_BGMREQNO2 = 4316; +sv.SW_SEREQNO = 4317; +sv.SW_BGMVOL = 4323; +sv.SW_SEVOL = 4325; + +sv.SW_SCRIPTNO0=4336; +sv.SW_SCRIPTNO1=4337; +sv.SW_SCRIPTNO2=4338; +sv.SW_SCRIPTNO3=4339; +sv.SW_SCRIPTNO4=4340; +sv.SW_SCRIPTNO5=4341; +sv.SW_SCRIPTNO6=4342; +sv.SW_SCRIPTNO7=4343; +sv.SW_SCRIPTNO8=4344; +sv.SW_SCRIPTNO9=4345; +sv.SW_SCRIPTNO10=4346; +sv.SW_SCRIPTNO11=4347; +sv.SW_SCRIPTNO12=4348; +sv.SW_SCRIPTNO13=4349; +sv.SW_SCRIPTNO14=4350; +sv.SW_SCRIPTNO15=4351; +sv.SW_MESWIN0TYPE = 4355; +sv.SW_MESMODE0 = 4356; +sv.SW_MASK1COLOR = 4390; +sv.SW_MASK1ALPHA = 4391; +sv.SW_MASK1PRI = 4392; +sv.SW_MASK1POSX = 4393; +sv.SW_MASK1POSY = 4394; +sv.SW_MASK1SIZEX = 4395; +sv.SW_MASK1SIZEY = 4396; + +sv.SW_PIC_ARCHIVENO1 = 3590; +sv.SW_PIC_FILENO1 = 3598; +sv.SW_PIC_REQ_ARCHIVENO1 = 4414; +sv.SW_PIC_REQ_FILENO1 = 4415; + +sv.SW_BGLINK = 4430; +sv.SW_BGLINK2 = 4431; + +sv.SW_CAP1POSX=4692; +sv.SW_CAP1POSY=4693; +sv.SW_CAP1SX=4694; +sv.SW_CAP1SY=4695; +sv.SW_CAP1SIZE=4696; +sv.SW_CAP1LX=4697; +sv.SW_CAP1LY=4698; +sv.SW_CAP1NO=4699; +sv.SW_CAP1PRI=4700; +sv.SW_CAP1DISPMODE=4701; +sv.SW_CAP1FADECT=4702; +sv.SW_CAP1FADETYPE=4703; +sv.SW_CAP1ROTZ=4704; +sv.SW_CAP1ALPHA=4705; +sv.SW_CAP1MASKNO=4706; +sv.SW_CAP1MASKFADERANGE=4707; +sv.SW_CAP1PRI2=4708; +sv.SW_CAP1FILTER=4709; + +sv.SW_CHA1POSX=4740; +sv.SW_CHA1POSY=4741; +sv.SW_CHA1ROTX=4742; +sv.SW_CHA1ROTY=4743; +sv.SW_CHA1ROTZ=4744; +sv.SW_CHA1SIZEX=4745; +sv.SW_CHA1SIZEY=4746; +sv.SW_CHA1ALPHA=4747; +sv.SW_CHA1FILTER=4748; +sv.SW_CHA1NO=4749; +sv.SW_CHA1PRI=4750; +sv.SW_CHA1POSE=4751; +sv.SW_CHA1FACE=4752; +sv.SW_CHA1EX=4753; +sv.SW_CHA1FADECT=4754; +sv.SW_CHA1FADETYPE=4755; +sv.SW_CHA1ANIME_EYE=4758; +sv.SW_CHA1ANIME_MOUTH=4759; +sv.SW_CHA1PRI2=4760; +sv.SW_CHA1BASESIZE=4761; + +sv.SW_BGEFF1_MASK_VERTEX1_X=4980; +sv.SW_BGEFF1_MASK_VERTEX1_Y=4981; +sv.SW_BGEFF1_SX=4988; +sv.SW_BGEFF1_SY=4989; +sv.SW_BGEFF1_SIZE=4990; +sv.SW_BGEFF1_PRI=4991; +sv.SW_BGEFF1_MASK_TYPE=4992; +sv.SW_BGEFF1_FADECT=4993; +sv.SW_BGEFF1_MODE=4994; +sv.SW_BGEFF1_ROTX=4995; +sv.SW_BGEFF1_ROTY=4996; +sv.SW_BGEFF1_ROTZ=4997; +sv.SW_BGEFF1_ALPHA=4998; +sv.SW_BGEFF1_MASKNO=4999; +sv.SW_BGEFF1_MASKFADERANGE=5000; +sv.SW_BGEFF1_PRI2=5001; +sv.SW_BGEFF1_FILTER=5002; +sv.SW_BGEFF1_POSX=5004; +sv.SW_BGEFF1_POSY=5005; + +sv.SW_MOSAIC = 5683; +sv.SW_MOSAIC_PRI = 5684; +sv.SW_FEATHERING=5685; +sv.SW_FEATHERING_PRI=5686; +sv.SW_FEATHERING2=5687; + + + +sv.SW_EFF_CAP_PRI=5695; +sv.SW_EFF_CAP_BUF=5696; +sv.SW_EFF_CAP_PRI2=5698; +sv.SW_EFF_CAP_BUF2=5700; +sv.SW_EFF_WAVE_PRI=5708; +sv.SW_EFF_WAVE_ALPHA = 5709; +sv.SW_MOVIEPRI = 5782; +sv.SW_MOVIEPRI3 = 5782; -- not sure if it's a script mistake or game maps them to the same var +sv.SW_MOVIE_LOADNO = 5783; +sv.SW_MOVIE_PLAYNO = 5795; +sv.SW_MOVIE_PLAYMODE = 5784; +sv.SW_MOVIE_PLAYVIEW = 5785; +sv.SW_MOVIEALPHA = 5787; +sv.SW_MOVIEALPHA3 = 5787; -- not sure if it's a script mistake or game maps them to the same var +sv.SW_MOVIEFRAME = 5790; +sv.SW_MOVIE_SHADER = 5791; +sv.SW_MOVIETOTALFRAME = 5792; + +sv.SW_MAP_ALPHA = 6000; +sv.SW_MAP_PRI = 6001; +sv.SW_MAP_DST_POS_X = 6002; +sv.SW_MAP_DST_POS_Y = 6003; +sv.SW_MAP_SELECTED_POOL_INDEX = 6004; +sv.SW_MAP_SELECTED_POOL_ID = 6005; +sv.SW_MAP_RENDER_ALPHA_1 = 6006; +sv.SW_MAP_RENDER_ALPHA_2 = 6007; +sv.SW_MAP_RENDER_ALPHA_3 = 6008; +sv.SW_MAP_ZOOM = 6009; +sv.SW_MAP_POS_X = 6010; +sv.SW_MAP_POS_Y = 6011; +sv.SW_MAP_SELECTED_POOL_TYPE = 6021; + +sv.SW_DELUSION_OVERLAY_BUF=6024; +sv.SW_DELUSION_CIRCLE_BUF=6025; +sv.SW_DELUSION_LIMIT=6026; +sv.SW_DELUSION_BG_COUNTER=6027; +sv.SW_DELUSION_STATE = 6028; +sv.SW_DELUSION_SPIN_COUNTER=6029; +sv.SW_DELUSION_PRI=6031; + + +sv.SW_YESNO_SELECTED = 6035; +sv.SW_YESNO_CT = 6036; +sv.SW_YESNO_TARGET_INDEX = 6037; +sv.SW_YESNO_PRI = 6038; + +sv.SW_MAP_PHOTO_SELECT_STATE = 6500; -- same + +sv.SW_UI_BTNGUIDE_REQ = 6500; +sv.SW_UI_BTNGUIDE_PROG = 6501; +sv.SW_UI_BTNGUIDE_TYPE = 6502; + +sv.SW_RENDERTARGET = 7100; + +sv.SF_CLR_FLAG = 1700; +sv.SF_CLR_END1 = 1701; +sv.SF_CLR_TRUE_CCLCC = 1710; +sv.SF_SELECTMODE = 2007; +sv.SF_SYSTEMMENUDISABLE = 2026; +sv.SF_SYSTEMMENUDISABLE2 = 2027; +sv.SF_SAVEDISABLE = 2030; +sv.SF_AUTOSAVEENABLE = 2031; + + +sv.SF_MESREVDISABLE = 2033; +sv.SF_GAMEPAUSE = 2034; +sv.SF_SAVEICON = 2037; +sv.SF_SYSTEMMENUDIRECT = 2039; + +sv.SF_MESSKIP = 2043; +sv.SF_MESALLSKIP = 2044; +sv.SF_MESREAD = 2045; +sv.SF_MOVIE_DRAWWAIT = 2046; +sv.SF_TITLEMODE = 2048; +sv.SF_TITLEEND = 2049; +sv.SF_UIHIDDEN = 2052; +sv.SF_SAVEPROTECTED = 2053; +sv.SF_SAVEALLPROTECTED = 2054; +sv.SF_SAVEPROTECTCHANGED = 2055; +sv.SF_MESSAVEPOINT_SSP = 2075; +sv.SF_ALBUMLOAD = 2088; +sv.SF_ALBUMLOAD_COMPLETE = 2089; +sv.SF_ALBUMEND = 2092; +sv.SF_SAVE_UNK1 = 2100; +sv.SF_SAVE_UNK2 = 2101; +sv.SF_SAVE_UNK3 = 2102; +sv.SW_ALBUM_LOADFILE = 2177; +sv.SW_ALBUM_LOADBUF = 2178; +sv.SF_RESTARTMASK = 2600; +sv.SF_RETURNTITLE = 2605; +sv.SF_SUBMENUEXIT = 2627; +sv.SF_BACKLOG_NOLOG = 2634; +sv.SF_MOVIEPLAY = 2652; +sv.SF_MOVIECANCEL = 2655; + +sv.SF_BG1DISP = 3200; +sv.SF_CAP1DISP = 3208; +sv.SF_CHA1DISP = 3210; +sv.SF_BGEFF1DISP = 3220; +sv.SF_MDL1SHDISP = 3257; +sv.SF_MOVIELOADPLAYFL = 3269; +sv.SF_MOVIEFL = 3275; +sv.SF_CHA1BGEFFECT = 3282; +sv.SF_MAPENABLE = 3800; +sv.SF_MAP_RENDER_UNK1 = 3801; +sv.SF_MAP_RENDER_UNK2 = 3802; +sv.SF_MAP_RENDER_UNK3 = 3803; +sv.SF_MAP_RENDER_UNK4 = 3804; +sv.SF_MAP_NO_CLAMP = 3805; +sv.SF_DELUSIONACTIVE = 3808; +sv.SF_DELUSIONSELECTED = 3809; \ No newline at end of file diff --git a/profiles/cclcc-switch/sprites.lua b/profiles/cclcc-switch/sprites.lua new file mode 100644 index 000000000..a6fb9cd86 --- /dev/null +++ b/profiles/cclcc-switch/sprites.lua @@ -0,0 +1,174 @@ +root.SpriteSheets = { + ["Album"] = { + Path = { Mount = "system", Id = 0 }, + DesignWidth = 4096, + DesignHeight = 4096 + }, + ["Backlog"] = { + Path = { Mount = "system", Id = 1 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["BacklogMask"] = { + Path = { Mount = "system", Id = 2 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, + ["ClearList"] = { + Path = { Mount = "system", Id = 3 }, + DesignWidth = 4096, + DesignHeight = 2324 + }, + ["Config"] = { + Path = { Mount = "system", Id = 4 }, + DesignWidth = 2048, + DesignHeight = 2538 + }, + ["ConfigEx"] = { + Path = { Mount = "system", Id = 25 }, + DesignWidth = 4096, + DesignHeight = 3272 + }, + ["Data"] = { + Path = { Mount = "system", Id = 26 }, + DesignWidth = 3072, + DesignHeight = 1536 + }, + ["Menu"] = { + Path = { Mount = "system", Id = 20 }, + DesignWidth = 2048, + DesignHeight = 1024 + }, + ["Font"] = { + Path = { Mount = "system", Id = 6 }, + DesignWidth = 4096, + DesignHeight = 11205 + }, + ["Font2"] = { + Path = { Mount = "system", Id = 8 }, + DesignWidth = 4096, + DesignHeight = 11205 + }, + ["LibraryMenu"] = { + Path = { Mount = "system", Id = 27 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["LoadMenu"] = { + Path = { Mount = "system", Id = 10 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["MapBG"] = { + Path = { Mount = "system", Id = 28 }, + DesignWidth = 3378, + DesignHeight = 1900 + }, + ["MapParts"] = { + Path = { Mount = "system", Id = 30 }, + DesignWidth = 2048, + DesignHeight = 3072 + }, + ["MenuBG"] = { + Path = { Mount = "system", Id = 31 }, + DesignWidth = 3000, + DesignHeight = 3000 + }, + ["MenuChip"] = { + Path = { Mount = "system", Id = 11 }, + DesignWidth = 4096, + DesignHeight = 2048 + }, + ["MesBox"] = { + Path = { Mount = "system", Id = 32 }, + DesignWidth = 1920, + DesignHeight = 600 + }, + ["MovMenu"] = { + Path = { Mount = "system", Id = 12 }, + DesignWidth = 3072, + DesignHeight = 1228 + }, + ["MusicMenu"] = { + Path = { Mount = "system", Id = 13 }, + DesignWidth = 3058, + DesignHeight = 1492 + }, + ["NamePlate"] = { + Path = { Mount = "system", Id = 33 }, + DesignWidth = 4096, + DesignHeight = 2048 + }, + ["QuickLoadMenu"] = { + Path = { Mount = "system", Id = 14 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["SaveMenu"] = { + Path = { Mount = "system", Id = 15 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["SysMesBox"] = { + Path = { Mount = "system", Id = 34 }, + DesignWidth = 2048, + DesignHeight = 1528 + }, + ["Tips"] = { + Path = { Mount = "system", Id = 16 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["TipsMask"] = { + Path = { Mount = "system", Id = 35 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, + ["Title"] = { + Path = { Mount = "system", Id = 17 }, + DesignWidth = 1920, + DesignHeight = 2160 + }, + ["TitleChip"] = { + Path = { Mount = "system", Id = 19 }, + DesignWidth = 2048, + DesignHeight = 2048 + }, + ["YesNoBG0"] = { + Path = { Mount = "system", Id = 38 }, + DesignWidth = 3840, + DesignHeight = 2160 + }, + ["YesNoBG1"] = { + Path = { Mount = "system", Id = 39 }, + DesignWidth = 3840, + DesignHeight = 2160 + }, + ["YesNoBG2"] = { + Path = { Mount = "system", Id = 40 }, + DesignWidth = 3840, + DesignHeight = 2160 + }, + ["YesNoBG3"] = { + Path = { Mount = "system", Id = 41 }, + DesignWidth = 3840, + DesignHeight = 2160 + }, + ["YesNoChip"] = { + Path = { Mount = "system", Id = 42 }, + DesignWidth = 1536, + DesignHeight = 2400 + }, + ["YesNoBlurMask"] = { + Path = { Mount = "mask", Id = 12 }, + DesignWidth = 512, + DesignHeight = 256 + }, + ["Help"] = { + Path = { Mount = "system", Id = 22 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, +}; + +root.Sprites = {}; diff --git a/profiles/cclcc-switch/vfs.lua b/profiles/cclcc-switch/vfs.lua new file mode 100644 index 000000000..757afdded --- /dev/null +++ b/profiles/cclcc-switch/vfs.lua @@ -0,0 +1,28 @@ +root.Vfs = { + Mounts = { + ["bg"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/bg_webp.mpk" }, + ["bgm"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/bgm_ninopus.mpk" }, + ["chara"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/chara_webp.mpk" }, + ["mask"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/mask.mpk" }, + ["mes"] = { + { + Path = root.BasePaths.RootGamedataDir .. "/cclcc-switch/script/mes00", + Order = root.BasePaths.RootGamedataDir .. "/cclcc-switch/script.cls", + Whitelist = ".*mes.*\\.msb", + }, + }, + ["movie"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/movie" }, + ["script"] = { + { + Path = root.BasePaths.RootGamedataDir .. "/cclcc-switch/script", + Order = root.BasePaths.RootGamedataDir .. "/cclcc-switch/script.cls", + Whitelist = ".*script.*\\.scx", + }, + }, + -- ["scriptdbg"] = {root.BasePaths.RootGamedataDir .. "/cclcc-switch/scriptdbg"}, + ["se"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/se_ninopus.mpk" }, + ["sysse"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/sysse_ninopus.mpk" }, + ["system"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/system_swi.mpk" }, + ["voice"] = { root.BasePaths.RootGamedataDir .. "/cclcc-switch/voice_ninopus.mpk" } + } +}; diff --git a/profiles/cclcc/hud/backlogmenu.lua b/profiles/cclcc/hud/backlogmenu.lua index 575cda463..54e5dc3c3 100644 --- a/profiles/cclcc/hud/backlogmenu.lua +++ b/profiles/cclcc/hud/backlogmenu.lua @@ -50,12 +50,6 @@ root.Sprites["BacklogHeader"] = { Bounds = { X = 962, Y = 1210, Width = 542, Height = 542 }, }; -root.SpriteSheets["BacklogMask"] = { - Path = { Mount = "system", Id = 2 }, - DesignWidth = 1920, - DesignHeight = 1080 -}; - root.Sprites["BacklogControls"] = { Sheet = "Backlog", Bounds = { X = 0, Y = 1151, Width = 1920, Height = 59 }, diff --git a/profiles/cclcc/hud/extramenus.lua b/profiles/cclcc/hud/extramenus.lua index aeb630662..9144a3f89 100644 --- a/profiles/cclcc/hud/extramenus.lua +++ b/profiles/cclcc/hud/extramenus.lua @@ -229,7 +229,8 @@ root.ExtraMenus = { MovieExtraVideosEnabled = true, MovieDiskExtraOp = "MovieDiskExtraOp", MovieDiskExtraOpHighlight = "MovieDiskExtraOpHighlight", - MovieDiskExtraOpPlayId = 62 + MovieDiskExtraOpPlayId = 62, + LibraryMenuId = 8 } }; diff --git a/profiles/cclcc/hud/tipsmenu.lua b/profiles/cclcc/hud/tipsmenu.lua index 184459825..ccb6ff6f3 100644 --- a/profiles/cclcc/hud/tipsmenu.lua +++ b/profiles/cclcc/hud/tipsmenu.lua @@ -78,12 +78,6 @@ root.Sprites["TipsHighlightedSprite"] = { Bounds = { X = 517, Y = 1208, Width = 740, Height = 40 } }; -root.SpriteSheets["TipsMask"] = { - Path = { Mount = "system", Id = 29 }, - DesignWidth = 1920, - DesignHeight = 1080 -}; - root.Sprites["TipsScrollThumb"] = { Sheet = "Tips", Bounds = { X = 2016, Y = 994, Width = 11, Height = 40 } diff --git a/profiles/cclcc/hud/titlemenu.lua b/profiles/cclcc/hud/titlemenu.lua index d43cf32bc..22e7e505b 100644 --- a/profiles/cclcc/hud/titlemenu.lua +++ b/profiles/cclcc/hud/titlemenu.lua @@ -49,6 +49,14 @@ root.TitleMenu = { HighlightAnimationDurationIn = 15/60, HighlightAnimationDurationOut = 15/60, ExtraDisabledTint = 0x703030, + NewButtonId = 0, + LoadButtonId = 10, + QLoadButtonId = 11, + TipsButtonId = 20, + LibraryButtonId = 21, + EndingListButtonId = 22, + ConfigButtonId = 30, + HelpButtonId = 40, }; for i = 0, 4 do diff --git a/profiles/cclcc/savedata.lua b/profiles/cclcc/savedata.lua index 57e5fcb91..23e3fa7e9 100644 --- a/profiles/cclcc/savedata.lua +++ b/profiles/cclcc/savedata.lua @@ -1,5 +1,5 @@ root.SaveData = { - Type = SaveDataType.CCLCC, + Type = SaveDataType.CCLCC_PS4, SaveFilePath = root.BasePaths.RootSavesDir .. "/cclcc/jpn/SAVEDATA.DAT", ScriptMessageData={ -- Pairs of line count and offset into read flags array {0x0, 0x0}, {0x6b, 0x0}, {0x8b, 0x6b}, {0x7e, 0xf6}, {0x144, 0x174}, {0x42, 0x2b8}, diff --git a/profiles/cclcc/scriptvars.lua b/profiles/cclcc/scriptvars.lua index 83dac7328..0626109f3 100644 --- a/profiles/cclcc/scriptvars.lua +++ b/profiles/cclcc/scriptvars.lua @@ -34,7 +34,23 @@ sv.SW_SVSCRNO1 = 3606; sv.SW_SVSCRNO2 = 3607; sv.SW_SVSCRNO3 = 3608; sv.SW_SVSCRNO4 = 3609; + +sv.SW_MAP_ZOOM = 6362; +sv.SW_MAP_POS_X = 6363; +sv.SW_MAP_POS_Y = 6364; +sv.SW_MAP_DST_POS_X = 6365; +sv.SW_MAP_DST_POS_Y = 6366; +sv.SW_MAP_SELECTED_POOL_INDEX = 6367; +sv.SW_MAP_SELECTED_POOL_ID = 6368; +sv.SW_MAP_SELECTED_POOL_TYPE = 6381; +sv.SW_MAP_PHOTO_SELECT_STATE = 6500; +sv.SW_MAP_RENDER_ALPHA_1 = 6369; +sv.SW_MAP_RENDER_ALPHA_2 = 6370; +sv.SW_MAP_RENDER_ALPHA_3 = 6371; sv.SW_DELUSION_STATE = 6412; +sv.SW_YESNO_SELECTED = 6432; +sv.SW_YESNO_CT = 6433; +sv.SW_YESNO_TARGET_INDEX = 6434; sv.SW_YESNO_PRI = 6437; sv.SW_UI_BTNGUIDE_REQ = 6500; sv.SW_UI_BTNGUIDE_PROG = 6501; @@ -44,4 +60,10 @@ sv.SF_SYSTEMMENUCAPTURE=1378; sv.SF_MOVIECANCEL = 1822; sv.SF_MOVIEFL = 2485; -sv.SF_CLR_TRUE_CCLCC=810; \ No newline at end of file +sv.SF_CLR_TRUE_CCLCC=810; +sv.SF_MAPENABLE = 2800; +sv.SF_MAP_RENDER_UNK1 = 2801; +sv.SF_MAP_RENDER_UNK2 = 2802; +sv.SF_MAP_RENDER_UNK3 = 2803; +sv.SF_MAP_RENDER_UNK4 = 2804; +sv.SF_MAP_NO_CLAMP = 2805; \ No newline at end of file diff --git a/profiles/cclcc/sprites.lua b/profiles/cclcc/sprites.lua index 71a591512..e555a5a8f 100644 --- a/profiles/cclcc/sprites.lua +++ b/profiles/cclcc/sprites.lua @@ -9,6 +9,11 @@ root.SpriteSheets = { DesignWidth = 2048, DesignHeight = 2048 }, + ["BacklogMask"] = { + Path = { Mount = "system", Id = 2 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, ["ClearList"] = { Path = { Mount = "system", Id = 3 }, DesignWidth = 4096, @@ -114,6 +119,11 @@ root.SpriteSheets = { DesignWidth = 2048, DesignHeight = 2048 }, + ["TipsMask"] = { + Path = { Mount = "system", Id = 29 }, + DesignWidth = 1920, + DesignHeight = 1080 + }, ["Title"] = { Path = { Mount = "system", Id = 30 }, DesignWidth = 1920, diff --git a/profiles/common/scriptvars.lua b/profiles/common/scriptvars.lua index 192aa7071..d3787b529 100644 --- a/profiles/common/scriptvars.lua +++ b/profiles/common/scriptvars.lua @@ -323,6 +323,7 @@ root.ScriptVars = { SW_MOVIEPRI4=6341, SW_MOVIEALPHA4=6342, SW_MOVIEFRAME=6343, + SW_MOVIE_SHADER=6344, SW_MOVIETOTALFRAME=6345, SW_MAP_ALPHA=6360, SW_MAP_PRI=6361, @@ -424,6 +425,9 @@ root.ScriptVars = { SF_SOUNDMENU=1357, SF_MOVIEMENU=1358, SF_ACHIEVEMENTMENU=1359, + SF_SAVE_UNK1 = 1370, + SF_SAVE_UNK2 = 1371, + SF_SAVE_UNK3 = 1372, SF_RESTARTMASK = 1800, SF_RETURNTITLE=1805, SF_MOVIEPLAY=1823, diff --git a/resources/cclcc-switch/icondata/icon.icns b/resources/cclcc-switch/icondata/icon.icns new file mode 100644 index 000000000..a9390f5b3 Binary files /dev/null and b/resources/cclcc-switch/icondata/icon.icns differ diff --git a/resources/cclcc-switch/icondata/icon.ico b/resources/cclcc-switch/icondata/icon.ico new file mode 100644 index 000000000..131153fba Binary files /dev/null and b/resources/cclcc-switch/icondata/icon.ico differ diff --git a/resources/cclcc-switch/icondata/icon.png b/resources/cclcc-switch/icondata/icon.png new file mode 100644 index 000000000..4b18eb6f4 Binary files /dev/null and b/resources/cclcc-switch/icondata/icon.png differ diff --git a/src/audio/lopusaudiostream.cpp b/src/audio/lopusaudiostream.cpp new file mode 100644 index 000000000..e2bd3b8c2 --- /dev/null +++ b/src/audio/lopusaudiostream.cpp @@ -0,0 +1,245 @@ +#include "lopusaudiostream.h" +#include "../log.h" + +using namespace Impacto::Io; + +namespace Impacto { +namespace Audio { + +AudioStream* LopusAudioStream::Create(Stream* stream) { + LopusAudioStream* result = new LopusAudioStream(); + uint32_t contextOffset; + + if (Io::ReadLE(stream) != 0x80000001) { + goto fail; + } + + result->BaseStream = stream; + result->BitDepth = 16; + + Io::ReadLE(result->BaseStream); // chunk size + Io::ReadLE(result->BaseStream); // version + result->ChannelCount = Io::ReadLE(result->BaseStream); + { + const uint16_t frameSize = + Io::ReadLE(result->BaseStream); // frame size, 0 if VBR + if (frameSize != 0) { + result->FrameSize = frameSize; + } + } + result->SampleRate = Io::ReadLE(result->BaseStream); + + // non standard sample rate case + if (result->SampleRate != 48000) { + assert(false); + result->SampleRate = 48000; + } + result->DataOffset = Io::ReadLE(result->BaseStream); + result->StartOffset = result->DataOffset + 8; + result->BaseStream->Seek(4, IoSeek::Cur); + contextOffset = Io::ReadLE(result->BaseStream); + result->PreSkipSampleCount = Io::ReadLE(result->BaseStream); + + result->BaseStream->Seek(2, IoSeek::Cur); // padding + + // context info chunk + if (contextOffset != 0 && + result->BaseStream->Seek(contextOffset, IoSeek::Set) == contextOffset && + Io::ReadLE(result->BaseStream) == 0x80000003) { + result->BaseStream->Seek(5, IoSeek::Cur); // unk + + result->Loop = Io::ReadU8(result->BaseStream) != 0; + result->BaseStream->Seek(2, IoSeek::Cur); // zeroes padding? + + result->Duration = + Io::ReadLE(result->BaseStream); // number of samples + result->LoopStart = Io::ReadLE(result->BaseStream); + result->LoopEnd = Io::ReadLE(result->BaseStream); + result->BaseStream->Seek(0x28, IoSeek::Cur); // zeroes padding + } + // TODO: multistream info chunk, possible but vgstream comment says it's rare + if (Io::ReadLE(result->BaseStream) != 0x80000005) { + ImpLog(LogLevel::Debug, LogChannel::Audio, + "Multistream info chunk encountered, but not handled"); + } + + // data info chunk + result->BaseStream->Seek(result->DataOffset, IoSeek::Set); + if (Io::ReadLE(result->BaseStream) != 0x80000004) { + goto fail; + } + + result->DataSize = Io::ReadLE(result->BaseStream); + result->SkipRemaining = (int)result->PreSkipSampleCount; + + { + int opusErr = 0; + result->Decoder = + opus_decoder_create(result->SampleRate, result->ChannelCount, &opusErr); + if (opusErr != OPUS_OK || !result->Decoder) { + goto fail; + } + } + + // build frame table for seek + { + uint32_t currentPos = result->StartOffset; + uint32_t end = result->StartOffset + result->DataSize; + uint64_t runningSamples = 0; + + while (currentPos < end) { + result->BaseStream->Seek(currentPos, IoSeek::Set); + const uint32_t frameSize = ReadBE(result->BaseStream); + result->BaseStream->Seek(4, IoSeek::Cur); + + uint32_t packetOffset = currentPos + 8; + if (frameSize == 0 || + static_cast(packetOffset) + frameSize > end) { + break; + } + + uint8_t peek[4] = {0, 0, 0, 0}; + const uint32_t peekLen = std::min(frameSize, 4U); + result->BaseStream->Read(peek, (int)peekLen); + + int frameSamples = opus_packet_get_nb_samples(peek, (int32_t)peekLen, + result->SampleRate); + + LopusFrameTableEntry entry = { + .SampleOffset = runningSamples, + .PacketOffset = packetOffset, + .PacketSize = frameSize, + }; + result->FrameTable.push_back(entry); + + runningSamples += static_cast(frameSamples); + currentPos = packetOffset + frameSize; + } + } + + return result; +fail: + if (result) { + delete result; + } + stream->Seek(0, IoSeek::Set); + return nullptr; +} + +LopusAudioStream::~LopusAudioStream() { + if (Decoder) { + opus_decoder_destroy(Decoder); + Decoder = nullptr; + } +} + +int LopusAudioStream::DecodeFrame(int frameIndex) { + static constexpr size_t kMaxFrameSamples = 5760; + + const LopusFrameTableEntry& entry = FrameTable[frameIndex]; + + std::vector packetBuf(entry.PacketSize); + BaseStream->Seek(entry.PacketOffset, IoSeek::Set); + BaseStream->Read(packetBuf.data(), (int)entry.PacketSize); + + PcmLeftover.assign(kMaxFrameSamples * ChannelCount, 0); + return opus_decode(Decoder, packetBuf.data(), (int32_t)entry.PacketSize, + PcmLeftover.data(), kMaxFrameSamples, 0); +} + +int LopusAudioStream::Read(void* out, int samples) { + if (!Decoder || samples <= 0) return 0; + + int16_t* outPtr = reinterpret_cast(out); + int written = 0; + + while (written < samples) { + int leftoverAvailable = + (int)(PcmLeftover.size() / ChannelCount) - PcmLeftoverOffset; + + if (leftoverAvailable > 0) { + int samplesToCopy = std::min(leftoverAvailable, samples - written); + std::memcpy(outPtr + (size_t)written * ChannelCount, + PcmLeftover.data() + (size_t)PcmLeftoverOffset * ChannelCount, + (size_t)samplesToCopy * ChannelCount * sizeof(int16_t)); + PcmLeftoverOffset += samplesToCopy; + written += samplesToCopy; + continue; + } + + if (PendingSeekFrameIndex.has_value()) { + const int frameIndex = *PendingSeekFrameIndex; + PendingSeekFrameIndex = std::nullopt; + + opus_decoder_ctl(Decoder, OPUS_RESET_STATE); + int decodedSamples = std::max(DecodeFrame(frameIndex), 0); + + if (decodedSamples == 0) { + PcmLeftover.clear(); + } else { + PcmLeftover.resize((size_t)decodedSamples * ChannelCount); + } + PcmLeftoverOffset = (int)std::min(PendingIntraFrameSkip, + (uint64_t)decodedSamples); + PendingIntraFrameSkip = 0; + continue; + } + + if (CurrentFrameIndex >= (int)FrameTable.size()) { + break; + } + + int decodedSamples = DecodeFrame(CurrentFrameIndex); + CurrentFrameIndex++; + if (decodedSamples <= 0) { + PcmLeftover.clear(); + PcmLeftoverOffset = 0; + continue; + } + PcmLeftover.resize((size_t)decodedSamples * ChannelCount); + PcmLeftoverOffset = 0; + if (SkipRemaining > 0) { + int skip = std::min(decodedSamples, SkipRemaining); + PcmLeftoverOffset = skip; + SkipRemaining -= skip; + } + } + + ReadPosition += written; + return written; +} + +void LopusAudioStream::Seek(int samples) { + if (!Decoder || FrameTable.empty()) return; + + samples = std::max(0, samples); + ReadPosition = samples; + const uint64_t targetRaw = + static_cast(samples) + PreSkipSampleCount; + + const auto it = + std::upper_bound(FrameTable.begin(), FrameTable.end(), targetRaw, + [](uint64_t value, const LopusFrameTableEntry& entry) { + return value < entry.SampleOffset; + }); + const int frameIndex = + std::max(static_cast(it - FrameTable.begin()) - 1, 0); + + const LopusFrameTableEntry& entry = FrameTable[frameIndex]; + + PcmLeftover.clear(); + PcmLeftoverOffset = 0; + SkipRemaining = 0; + + PendingSeekFrameIndex = frameIndex; + PendingIntraFrameSkip = + targetRaw > entry.SampleOffset ? targetRaw - entry.SampleOffset : 0; + + CurrentFrameIndex = frameIndex + 1; +} + +bool LopusAudioStream::_registered = + AudioStream::AddAudioStreamCreator(&LopusAudioStream::Create); + +} // namespace Audio +} // namespace Impacto \ No newline at end of file diff --git a/src/audio/lopusaudiostream.h b/src/audio/lopusaudiostream.h new file mode 100644 index 000000000..253489aca --- /dev/null +++ b/src/audio/lopusaudiostream.h @@ -0,0 +1,51 @@ +#pragma once + +#include "audiostream.h" +#include +#include "../impacto.h" + +namespace Impacto { +namespace Audio { + +struct LopusFrameTableEntry { + uint64_t SampleOffset; + uint32_t PacketOffset; + uint32_t PacketSize; +}; +class LopusAudioStream : public AudioStream { + public: + ~LopusAudioStream(); + + int Read(void* out, int samples) override; + void Seek(int samples) override; + + private: + static AudioStream* Create(Io::Stream* stream); + LopusAudioStream() {} + void InitWithInfo() {} + int DecodeFrame(int frameIndex); + + std::vector FrameTable; + + uint32_t DataOffset = 0; + uint32_t StartOffset = 0; + uint32_t DataSize = 0; + uint32_t PreSkipSampleCount = 0; + std::optional FrameSize = std::nullopt; + int CurrentFrameIndex = 0; + + uint64_t PendingIntraFrameSkip = 0; + std::optional PendingSeekFrameIndex = std::nullopt; + + OpusDecoder* Decoder = 0; + + std::vector PcmLeftover; + int PcmLeftoverOffset = 0; + int SkipRemaining = 0; + bool Loop = false; + + static bool _registered; +}; + +} // namespace Audio +} // namespace Impacto \ No newline at end of file diff --git a/src/background2d.cpp b/src/background2d.cpp index 28aed828c..f48d8dfc5 100644 --- a/src/background2d.cpp +++ b/src/background2d.cpp @@ -466,6 +466,7 @@ void Background2D::UpdateState(const int bgId) { switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_BG1FILTER + structOffset); break; @@ -632,6 +633,7 @@ void Capture2D::UpdateState(const int capId) { switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_CAP1FILTER + structOffset); break; @@ -655,7 +657,7 @@ void Capture2D::Render(const int layer) { Background2D::BgTransformState Background2D::BgTransformState::GetBgEffTransformState(int bgEffId) { assert(ScrWorkBgEffStructSize >= 30); - assert(ScrWorkBgEffOffsetStructSize >= 20); + assert(ScrWorkBgEffOffsetStructSize >= 18); const glm::vec2 resolutionScale = {Profile::Game::DesignWidth / 1280.0f, Profile::Game::DesignHeight / 720.0f}; @@ -731,6 +733,7 @@ void BackgroundEffect2D::UpdateState(const int bgId) { // Set tint switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_BGEFF1_FILTER + structOffset); break; diff --git a/src/character2d.cpp b/src/character2d.cpp index 2d8462abb..70f734d47 100644 --- a/src/character2d.cpp +++ b/src/character2d.cpp @@ -358,6 +358,9 @@ void Character2D::UpdateState(const int chaId) { Tint.a *= ScrWork[SW_CHA1FADECT + structOffset] / 256.0f; } + // clamping alpha cause engine does and scripts can send garbage + Tint.a = std::clamp(Tint.a, 0.0f, 1.0f); + Face = ScrWork[SW_CHA1FACE + structOffset] << 16; if (ScrWork[SW_CHA1ANIME_EYE + structOffset] == 0xFF) { diff --git a/src/data/savesystem.cpp b/src/data/savesystem.cpp index 5b619f768..7aedf9caf 100644 --- a/src/data/savesystem.cpp +++ b/src/data/savesystem.cpp @@ -89,7 +89,30 @@ class SaveFileWriter : public Loadable { } }; -static std::variant Loader; +class QuickSaveFileWriter : public Loadable { + friend Loadable; + + protected: + void UnloadSync() {} + SaveError LoadSync() { + return Implementation ? Implementation->WriteQuickSaveFile() + : SaveError::Failed; + } + + void MainThreadOnLoad(SaveError result) { + // Let's not report errors until we finalize the implementation + if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::CC && + Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC) { + result = SaveError::OK; + } + + ScrWork[SW_SAVEERRORCODE] = (int)result; + } +}; + +static std::variant + Loader; template void ExecuteLoader() { @@ -108,6 +131,7 @@ LoadStatus GetLoadStatus() { void MountSaveFile() { ExecuteLoader(); } void CheckSaveFile() { ExecuteLoader(); } void WriteSaveFile() { ExecuteLoader(); } +void WriteQuickSaveFile() { ExecuteLoader(); } void InitializeSystemData() { if (Implementation) Implementation->InitializeSystemData(); diff --git a/src/data/savesystem.h b/src/data/savesystem.h index 8506b8943..8de5b99c4 100644 --- a/src/data/savesystem.h +++ b/src/data/savesystem.h @@ -17,7 +17,8 @@ namespace SaveSystem { enum class SaveDataType : int { None, CHLCC, - CCLCC, + CCLCC_PS4, + CCLCC_SWITCH, MO6TW, }; enum SaveFlagsMode { WriteProtect = 1 }; @@ -116,6 +117,7 @@ class SaveSystemBase { virtual void SaveThumbnailData() = 0; virtual SaveError WriteSaveFile() = 0; + virtual SaveError WriteQuickSaveFile() { return SaveError::OK; }; virtual uint32_t GetSavePlayTime(SaveType type, int id) const = 0; virtual uint8_t GetSaveFlags(SaveType type, int id) const = 0; virtual void SetSaveFlags(SaveType type, int id, uint8_t flags) = 0; @@ -193,6 +195,7 @@ void LoadEntry(SaveType type, int id); void LoadMemoryNew(LoadProcess process); void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType = 0); void WriteSaveFile(); +void WriteQuickSaveFile(); uint32_t GetSavePlayTime(SaveType type, int id); uint8_t GetSaveFlags(SaveType type, int id); void SetSaveFlags(SaveType type, int id, uint8_t flags); diff --git a/src/data/tipssystem.cpp b/src/data/tipssystem.cpp index 5b4ef3973..81f8507a8 100644 --- a/src/data/tipssystem.cpp +++ b/src/data/tipssystem.cpp @@ -1,7 +1,10 @@ #include "tipssystem.h" #include "../profile/data/tipssystem.h" +#include "../profile/vm.h" #include "../vm/vm.h" +#include "../vm/sc3stream.h" +#include "../text/text.h" #include namespace Impacto { @@ -11,71 +14,62 @@ using namespace Impacto::Profile::TipsSystem; TipsComparator::TipsComparator(uint32_t tipsTableId, uint32_t sortStringIndex, int tipIdStrIndex) - : SortString(nullptr), TipIdStrIndex(tipIdStrIndex) { - auto [scrBufId, offset] = + : TipIdStrIndex(tipIdStrIndex) { + auto [buffers, bufferId, ip] = Vm::ScriptGetTextTableStrAddress(tipsTableId, sortStringIndex); - SortString = &Vm::ScriptBuffers[scrBufId][offset]; - int i = 0; + Vm::Sc3Stream sortStream = &buffers[bufferId][ip]; + int distance = 0; - while (SortString[i] != 0xFF) { - if (SortString[i] & 0x80) { - uint16_t sc3Char = SDL_Swap16BE(UnalignedRead(SortString + i)); - Sc3SortMap[sc3Char] = distance++; - i += 2; + + while (sortStream.PeekU8() != 0xff) { + StringToken token; + token.Read(sortStream); + if (token.Type == STT_Character) { + Sc3SortMap[token.Val_Int] = distance++; } else { ImpLogSlow(LogLevel::Warning, LogChannel::VM, - "TipsComparator: SC3 Tag Found in Sort String\n", - SortString[i]); - i++; + "TipsComparator: SC3 Tag Found in Sort String\n", token.Type); } } } bool TipsComparator::operator()(int a, int b) const { - auto* aRecord = TipsSystem::GetTipRecord(a); - auto* bRecord = TipsSystem::GetTipRecord(b); - uint32_t tipsScrBufId = TipsSystem::GetTipsScriptBufferId(); - uint8_t* aString = - &Vm::ScriptBuffers[tipsScrBufId][aRecord->StringAdr[TipIdStrIndex]]; - uint8_t* bString = - &Vm::ScriptBuffers[tipsScrBufId][bRecord->StringAdr[TipIdStrIndex]]; - - int aIndex = 0; - int bIndex = 0; - - while (aString[aIndex] != 0xff && bString[bIndex] != 0xff) { - if ((aString[aIndex] & 0x80) == 0) { - aIndex++; - continue; - } - if ((bString[bIndex] & 0x80) == 0) { - bIndex++; - continue; - } - uint16_t aSc3Char = SDL_Swap16BE(UnalignedRead(aString + aIndex)); - aIndex += 2; - - uint16_t bSc3Char = SDL_Swap16BE(UnalignedRead(bString + bIndex)); - bIndex += 2; - if (aSc3Char != bSc3Char) { - auto aSortValue = Sc3SortMap.find(aSc3Char); - auto bSortValue = Sc3SortMap.find(bSc3Char); + Vm::Sc3Stream aString = TipsSystem::GetTextStringStream(a, TipIdStrIndex); + Vm::Sc3Stream bString = TipsSystem::GetTextStringStream(b, TipIdStrIndex); + + while (aString.PeekU8() != 0xff && bString.PeekU8() != 0xff) { + StringToken aToken; + StringToken bToken; + aToken.Read(aString); + bToken.Read(bString); + + if (aToken.Val_Int != bToken.Val_Int) { + auto aSortValue = Sc3SortMap.find(aToken.Val_Int); + auto bSortValue = Sc3SortMap.find(bToken.Val_Int); if (aSortValue != Sc3SortMap.end() && bSortValue != Sc3SortMap.end()) { return aSortValue->second < bSortValue->second; } else { ImpLogSlow(LogLevel::Error, LogChannel::VM, "TipsComparator: Character Not Found in Sort String\n", - aSc3Char, bSc3Char); - return aSc3Char < bSc3Char; + aToken.Val_Int, bToken.Val_Int); + return aToken.Val_Int < bToken.Val_Int; } } } // If strings are all the same, return the shorter one - return aString[aIndex] == 0xff && bString[bIndex] != 0xff; + return aString.PeekU8() == 0xff && bString.PeekU8() != 0xff; } void Init() { Configure(); } +Vm::Sc3Stream GetTextStringStream(size_t record, size_t stringIndex) { + if (!Implementation) return {nullptr}; + auto [buffers, bufferId] = GetTipsScriptBufferCtx(); + auto const& records = *GetTipRecords(); + return Vm::Sc3Stream{ + &buffers[bufferId][records[record].StringAdr[stringIndex]]}; +} + void DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr, uint32_t tipsDataSize) { if (Implementation) @@ -137,11 +131,14 @@ size_t GetTipCount() { return 0; } -uint8_t GetTipsScriptBufferId() { - if (Implementation) return Implementation->ScriptBufferId; +TipsBufferCtx GetTipsScriptBufferCtx() { + if (Implementation) + return {.Buffers = + Profile::Vm::UseMsbStrings ? Vm::MsbBuffers : Vm::ScriptBuffers, + .BufferId = Implementation->ScriptBufferId}; ImpLog(LogLevel::Warning, LogChannel::VMStub, - "{:s}: tips system not implemented, returning 0\n", __func__); - return 0; + "{:s}: tips system not implemented, returning empty ctx\n", __func__); + return TipsBufferCtx{}; } std::vector& GetNewTipsIndices() { diff --git a/src/data/tipssystem.h b/src/data/tipssystem.h index 6824650bb..a58faa2ac 100644 --- a/src/data/tipssystem.h +++ b/src/data/tipssystem.h @@ -5,6 +5,7 @@ #include #include #include "../log.h" +#include "../vm/sc3stream.h" namespace Impacto { namespace TipsSystem { @@ -40,20 +41,23 @@ class TipsSystemBase { virtual void SetTipNewState(size_t id, bool state) = 0; virtual bool GetTipLockedState(size_t id) = 0; - std::vector Records; std::vector NewTipsIndices; size_t TipEntryCount = 0; uint8_t ScriptBufferId = 0; }; +struct TipsBufferCtx { + std::span> Buffers; + uint8_t BufferId; +}; + struct TipsComparator { TipsComparator(uint32_t tipsTableId, uint32_t sortStringIndex, int tipIdStrIndex); bool operator()(int a, int b) const; - uint8_t* SortString; int TipIdStrIndex; - ankerl::unordered_dense::map Sc3SortMap; + ankerl::unordered_dense::map Sc3SortMap; }; inline std::unique_ptr Implementation; @@ -65,13 +69,14 @@ void UpdateTipRecords(); void SetTipLockedState(size_t id, bool state); void SetTipUnreadState(size_t id, bool state); void SetTipNewState(size_t id, bool state); -uint8_t GetTipsScriptBufferId(); +TipsBufferCtx GetTipsScriptBufferCtx(); bool GetTipLockedState(size_t id); std::vector* GetTipRecords(); TipsDataRecord* GetTipRecord(size_t id); size_t GetTipCount(); std::vector& GetNewTipsIndices(); +Vm::Sc3Stream GetTextStringStream(size_t record, size_t stringIndex); } // namespace TipsSystem } // namespace Impacto \ No newline at end of file diff --git a/src/game.cpp b/src/game.cpp index ba6a255ae..508b7ec9c 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -279,7 +279,8 @@ void UpdateGameState(float dt) { UI::CCLCC::DelusionTrigger::GetInstance().UpdateDragging(dt); } Vm::ChkMesSkip(dt); - if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) { + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { UI::GameSpecific::UpdateCCButtonGuide(dt); } } @@ -366,7 +367,8 @@ void UpdateSystem(float dt) { Effects::WaveBG.Update(updateInterval); } const bool isCC = - +Profile::Vm::GameInstructionSet == +Vm::InstructionSet::CC; + Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch; if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) { Effects::WaveEFF.Update(updateInterval); } @@ -434,7 +436,8 @@ static void RenderMain() { Background2D::LastRenderedBackground = nullptr; UI::GameSpecific::RenderEarlyMain(); for (uint32_t layer = 0; layer <= Profile::Game::LayerCount; layer++) { - if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) { + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { const int renderTarget = ScrWork[SW_RENDERTARGET + layer]; if (0 <= renderTarget && renderTarget <= MaxFramebuffers) { Renderer->SetFramebuffer(renderTarget); @@ -540,7 +543,8 @@ static void RenderMain() { if (Profile::Game::UseWaveEffects) { const bool isCC = - +Profile::Vm::GameInstructionSet == +Vm::InstructionSet::CC; + Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch; if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) { if (ScrWork[SW_EFF_WAVE_PRI] == static_cast(layer)) { std::optional alpha = std::nullopt; @@ -643,7 +647,9 @@ void Render() { } // System menu capture - if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC && + if ((Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == + Vm::InstructionSet::LCCSwitch) && GetFlag(SF_SYSTEMMENUCAPTURE)) { Renderer->CaptureScreencap( static_cast(UI::SystemMenuPtr) @@ -659,7 +665,9 @@ void Render() { break; } case DrawComponentType::Main: { - if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC && + if ((Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == + Vm::InstructionSet::LCCSwitch) && !(!GetFlag(SF_SELECTMODE) || (GetFlag(SF_SYSTEMMENUCAPTURE) && ScrWork[SW_RESTARTMASK] != 0x100))) { @@ -682,7 +690,9 @@ void Render() { break; } case DrawComponentType::SystemIcons: { - if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC) { + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == + Vm::InstructionSet::LCCSwitch) { UI::GameSpecific::RenderCCButtonGuide(); } diff --git a/src/games/cc/backlogmenu.cpp b/src/games/cc/backlogmenu.cpp index 00b5f556a..3344e2d01 100644 --- a/src/games/cc/backlogmenu.cpp +++ b/src/games/cc/backlogmenu.cpp @@ -102,7 +102,7 @@ void BacklogMenu::Render() { MainScrollbar.Render(); if (ScrWork[SW_SYSSUBMENUNO] == 1) { - CommonMenu::DrawOverlay(); + CommonMenu::DrawOverlay(OpenedAsDirect ? opacity : 1.0f); if (GetFlag(SF_TITLEMODE)) { CommonMenu::DrawSmoke(Profile::CCLCC::SystemMenu::SmokeOpacityNormal * (1.0f - FadeAnimation.Progress)); diff --git a/src/games/cc/sysmesbox.cpp b/src/games/cc/sysmesbox.cpp index ad7270938..74795c755 100644 --- a/src/games/cc/sysmesbox.cpp +++ b/src/games/cc/sysmesbox.cpp @@ -4,6 +4,7 @@ #include "../../profile/games/cc/sysmesbox.h" #include "../../profile/dialogue.h" #include "../../profile/game.h" +#include "../../profile/vm.h" #include "../../profile/scriptvars.h" #include "../../mem.h" #include "../../renderer/renderer.h" @@ -237,7 +238,8 @@ void SysMesBox::Init() { void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; Messages[MessageCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, @@ -250,7 +252,8 @@ void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { void SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; Choices[ChoiceCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, diff --git a/src/games/cc/titlemenu.cpp b/src/games/cc/titlemenu.cpp index dc67944bc..838f28f97 100644 --- a/src/games/cc/titlemenu.cpp +++ b/src/games/cc/titlemenu.cpp @@ -220,7 +220,7 @@ void TitleMenu::Update(float dt) { if (State != Hidden && GetFlag(SF_TITLEMODE)) { switch (ScrWork[SW_TITLEMODE]) { - using enum TitleMenuMode::Mode; + using enum TitleMenuMode::PS4::Mode; case PressToStart: { PressToStartAnimation.DurationIn = PressToStartAnimDurationIn; PressToStartAnimation.DurationOut = PressToStartAnimDurationOut; @@ -251,7 +251,7 @@ void TitleMenu::Update(float dt) { void TitleMenu::Render() { if (State != Hidden && GetFlag(SF_TITLEMODE)) { switch (ScrWork[SW_TITLEMODE]) { - using enum TitleMenuMode::Mode; + using enum TitleMenuMode::PS4::Mode; case PressToStart: { DrawMainBackground(true); DrawStartButton(); diff --git a/src/games/cc/titlemenu.h b/src/games/cc/titlemenu.h index 3d88bc80a..3f037051d 100644 --- a/src/games/cc/titlemenu.h +++ b/src/games/cc/titlemenu.h @@ -59,6 +59,7 @@ class TitleMenu : public Menu { }; namespace TitleMenuMode { +namespace PS4 { enum Mode : uint8_t { Invisible = 0, PressToStart = 1, @@ -67,10 +68,25 @@ enum Mode : uint8_t { FadingOut = 4, SubMenu = 5, InitialFade = 11, + ClearList = 13 +}; +} +namespace Switch { +enum Mode : uint8_t { + Invisible = 0, + PressToStart = 2, + StartTransition = 3, + Main = 10, + SubMenu = 11, + FadingOut = 12, ClearList = 13, + FadingIn = 16, + InitialFade = 18, }; } +} // namespace TitleMenuMode + } // namespace CC } // namespace UI } // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/delusiontrigger.cpp b/src/games/cclcc/delusiontrigger.cpp index 326677ee1..ae59f6202 100644 --- a/src/games/cclcc/delusiontrigger.cpp +++ b/src/games/cclcc/delusiontrigger.cpp @@ -47,9 +47,10 @@ bool DelusionTrigger::Show(int bgOverlayBgBufferId, int circlesBgBufferId, ScrWork[SW_DELUSION_SPIN_COUNTER] = 0x40; ScrWork[SW_DELUSION_LIMIT] = availableDelusions; SetFlag(SF_DELUSIONACTIVE, 1); - ScrWork[6344] = 48; + ScrWork[SW_MOVIE_SHADER] = 48; SetFlag(SF_DELUSIONSELECTED, 0); - ScrWork[6418] = 960; + // TODO: Ps Vita only + // ScrWork[6418] = 960; ResetDraggingPress(); if (!Video::Players[0]->IsPlaying) { ScrWork[SW_DELUSION_BG_COUNTER] = 32; @@ -59,7 +60,7 @@ bool DelusionTrigger::Show(int bgOverlayBgBufferId, int circlesBgBufferId, void DelusionTrigger::Hide() { SetFlag(SF_DELUSIONACTIVE, 0); - ScrWork[6344] = 0; + ScrWork[SW_MOVIE_SHADER] = 0; ResetDraggingPress(); } diff --git a/src/games/cclcc/helpmenu.cpp b/src/games/cclcc/helpmenu.cpp index b4909a795..dbc1cc85c 100644 --- a/src/games/cclcc/helpmenu.cpp +++ b/src/games/cclcc/helpmenu.cpp @@ -96,14 +96,16 @@ void HelpMenu::UpdateInput(float dt) { SetFlag(SF_MANUALLOAD_COMPLETE, 0); SetFlag(SF_MANUALLOAD_END, 1); } - if (ManualPages.size() > 1) { + + auto manualPages = ManualPages.size(); + + if (manualPages > 1) { bool prevBtnPressed = ((PADinputButtonWentDown | PADinputMouseWentDown) & PADcustom[38]) || Input::MouseWheelDeltaY < 0 || Input::TouchFlickLeft; if (State == Shown && prevBtnPressed && FadeAnimation.Progress == 1.0f) { PreviousPage = CurrentPage; - CurrentPage = - (int)((CurrentPage - 1 + ManualPages.size()) % ManualPages.size()); + CurrentPage = (int)((CurrentPage - 1 + manualPages) % manualPages); FadeAnimation.StartIn(true); NextPageAnimation.StartIn(true); IsGoingNext = false; @@ -114,7 +116,7 @@ void HelpMenu::UpdateInput(float dt) { Input::MouseWheelDeltaY > 0 || Input::TouchFlickRight; if (State == Shown && nextBtnPressed && FadeAnimation.Progress == 1.0f) { PreviousPage = CurrentPage; - CurrentPage = (CurrentPage + 1) % ManualPages.size(); + CurrentPage = (CurrentPage + 1) % manualPages; FadeAnimation.StartIn(true); NextPageAnimation.StartIn(true); IsGoingNext = true; diff --git a/src/games/cclcc/librarymenu.cpp b/src/games/cclcc/librarymenu.cpp index dd2a250f3..3f55b2993 100644 --- a/src/games/cclcc/librarymenu.cpp +++ b/src/games/cclcc/librarymenu.cpp @@ -154,7 +154,7 @@ void LibraryMenu::Update(float dt) { if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown) { Hide(); } else if (ScrWork[SW_SYSSUBMENUCT] > 0 && State == Hidden && - (ScrWork[SW_SYSSUBMENUNO] == 8)) { + (ScrWork[SW_SYSSUBMENUNO] == LibraryMenuId)) { Show(); } const auto* albumMenuPtr = static_cast(UI::AlbumMenuPtr); @@ -163,7 +163,7 @@ void LibraryMenu::Update(float dt) { const bool cgViewerActive = CurrentLibraryMenu == LibraryMenuPageType::Album && albumMenuPtr->CGViewer; - if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == 8) { + if (State == Shown && ScrWork[SW_SYSSUBMENUNO] == LibraryMenuId) { if (!moviePlaying && !cgViewerActive) { UpdateInput(dt); if ((Vm::Interface::PADinputButtonWentDown & Vm::Interface::PAD1B) || @@ -288,7 +288,7 @@ void LibraryMenu::Render() { albumMenuPtr->RenderCGViewer(); } - if (ScrWork[SW_SYSSUBMENUNO] == 8) { + if (ScrWork[SW_SYSSUBMENUNO] == LibraryMenuId) { CommonMenu::DrawOverlay(); if (GetFlag(SF_TITLEMODE)) { CommonMenu::DrawSmoke(Profile::CCLCC::SystemMenu::SmokeOpacityNormal * diff --git a/src/games/cclcc/mapsystem.cpp b/src/games/cclcc/mapsystem.cpp index dd3274029..54f6fdc39 100644 --- a/src/games/cclcc/mapsystem.cpp +++ b/src/games/cclcc/mapsystem.cpp @@ -25,8 +25,6 @@ inline float toFlt(double d) { return static_cast(d); } inline float toFlt(int d) { return static_cast(d); } inline int toInt(float f) { return static_cast(f); } -// TODO, add the scrwork and flag indices to the scriptvars file - // TODO, move this crap to the lua file constexpr int MapPhotoIdMap[11][4] = { // index 0 is map part for photos @@ -154,9 +152,9 @@ void MapSystem::MapInit() { // Silence unused var warning, this table is probably going to be useful later (void)MapArticleOffsets; - ScrWork[6369] = 0; - ScrWork[6370] = 0; - ScrWork[6371] = 0; + ScrWork[SW_MAP_RENDER_ALPHA_1] = 0; + ScrWork[SW_MAP_RENDER_ALPHA_2] = 0; + ScrWork[SW_MAP_RENDER_ALPHA_3] = 0; MapPartsMax = 0; @@ -171,11 +169,11 @@ void MapSystem::MapInit() { int i_i = static_cast(i); MapGroup[i] = MapGroupStruct{i_i, i_i, i_i}; } - SetFlag(2801, 0); - SetFlag(2802, 0); - SetFlag(2803, 0); - SetFlag(2804, 0); - SetFlag(2805, 0); + SetFlag(SF_MAP_RENDER_UNK1, 0); + SetFlag(SF_MAP_RENDER_UNK2, 0); + SetFlag(SF_MAP_RENDER_UNK3, 0); + SetFlag(SF_MAP_RENDER_UNK4, 0); + SetFlag(SF_MAP_NO_CLAMP, 0); } int MapSystem::MapLoad(uint8_t* data) { @@ -905,7 +903,7 @@ void MapSystem::HandlePoolLeftRightNav(int maxPoolRow, int poolType, } bool MapSystem::MapPlayerPhotoSelect(int unused) { - ScrWork[6500] = 2; + ScrWork[SW_MAP_PHOTO_SELECT_STATE] = 2; int oldHover = HoverMapPoolIdx; if (unused == 0) { int poolType = MapPool[0].type; @@ -948,10 +946,10 @@ bool MapSystem::MapPlayerPhotoSelect(int unused) { } if (SelectedMapPoolIdx != 0xff) { - ScrWork[6367] = SelectedMapPoolIdx; - ScrWork[6368] = MapPool[SelectedMapPoolIdx].id; - ScrWork[6381] = MapPool[SelectedMapPoolIdx].type; - ScrWork[6500] = 0; + ScrWork[SW_MAP_SELECTED_POOL_INDEX] = SelectedMapPoolIdx; + ScrWork[SW_MAP_SELECTED_POOL_ID] = MapPool[SelectedMapPoolIdx].id; + ScrWork[SW_MAP_SELECTED_POOL_TYPE] = MapPool[SelectedMapPoolIdx].type; + ScrWork[SW_MAP_PHOTO_SELECT_STATE] = 0; MapPool[SelectedMapPoolIdx].button.HasFocus = false; HoverMapPoolIdx = 0xff; SelectedMapPoolIdx = 0xff; @@ -981,7 +979,7 @@ void getMapPos(float newSize, float newX, float newY, float& setX, float& setY, float offsetMapX = newX - mapWidth * 0.5f; float offsetMapY = newY - mapHeight * 0.5f; - if (!GetFlag(2805)) { + if (!GetFlag(SF_MAP_NO_CLAMP)) { if (offsetMapX > MapBgSprite.Sheet.DesignWidth - mapWidth) offsetMapX = MapBgSprite.Sheet.DesignWidth - mapWidth; @@ -1017,8 +1015,8 @@ void MapSystem::MapZoomInit(int mapX, int mapY, int size) { toFlt(size * 4)}; float zoomSize = 0.0f; - getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]), - MapPosTransitions[MapZoom].Start.x, + getMapPos(toFlt(ScrWork[SW_MAP_ZOOM]), toFlt(ScrWork[SW_MAP_POS_X]), + toFlt(ScrWork[SW_MAP_POS_Y]), MapPosTransitions[MapZoom].Start.x, MapPosTransitions[MapZoom].Start.y, zoomSize, toFlt(mapX) / 1280.0f, toFlt(mapY) / 720.0f); @@ -1101,10 +1099,11 @@ bool MapSystem::MapZoomMain() { MapPosTransitions[MapZoom].Current.size = MapPosTransitions[MapZoom].End.size; } - ScrWork[6362] = (toInt(MapPosTransitions[MapZoom].Current.size) + 5) / 4; - float clampedSize = (ScrWork[6362] < 100) ? 100.0f - : (ScrWork[6362] > 800) ? 800.0f - : ScrWork[6362]; + ScrWork[SW_MAP_ZOOM] = + (toInt(MapPosTransitions[MapZoom].Current.size) + 5) / 4; + float clampedSize = (ScrWork[SW_MAP_ZOOM] < 100) ? 100.0f + : (ScrWork[SW_MAP_ZOOM] > 800) ? 800.0f + : ScrWork[SW_MAP_ZOOM]; float newMapPosX = MapPosTransitions[MapZoom].Current.x - (MapPosTransitions[MapZoom].End.x / 1920.0f + -0.5f) * (MapBgSprite.Sheet.DesignWidth * 100.0f) / clampedSize; @@ -1114,8 +1113,8 @@ bool MapSystem::MapZoomMain() { (MapBgSprite.Sheet.DesignHeight * 100.0f) / clampedSize; - ScrWork[6363] = static_cast(newMapPosX); - ScrWork[6364] = static_cast(newMapPosY); + ScrWork[SW_MAP_POS_X] = static_cast(newMapPosX); + ScrWork[SW_MAP_POS_Y] = static_cast(newMapPosY); return toInt(MapPosTransitions[MapZoom].End.size) == toInt(MapPosTransitions[MapZoom].Current.size); } @@ -1180,10 +1179,12 @@ bool MapSystem::MapZoomMain3() { MapPosTransitions[MapZoom3].Start.y) * ((float)MapZoomCt / MapZoomCtMax); - ScrWork[6362] = + ScrWork[SW_MAP_ZOOM] = static_cast((MapPosTransitions[MapZoom3].Current.size + 5) / 10); - ScrWork[6363] = static_cast(MapPosTransitions[MapZoom3].Current.x); - ScrWork[6364] = static_cast(MapPosTransitions[MapZoom3].Current.y); + ScrWork[SW_MAP_POS_X] = + static_cast(MapPosTransitions[MapZoom3].Current.x); + ScrWork[SW_MAP_POS_Y] = + static_cast(MapPosTransitions[MapZoom3].Current.y); return MapZoomCt == MapZoomCtMax; } bool MapSystem::MapZoomInit3(int setMapX, int setMapY, int setMapSize, @@ -1197,8 +1198,8 @@ bool MapSystem::MapZoomInit3(int setMapX, int setMapY, int setMapSize, float zoomSize = 0.0f; float mapX = 0.0f; float mapY = 0.0f; - getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]), - mapX, mapY, zoomSize, 0.5, 0.5); + getMapPos(toFlt(ScrWork[SW_MAP_ZOOM]), toFlt(ScrWork[SW_MAP_POS_X]), + toFlt(ScrWork[SW_MAP_POS_Y]), mapX, mapY, zoomSize, 0.5, 0.5); MapPosTransitions[MapZoom3].Start.size = 10 * zoomSize; MapPosTransitions[MapZoom3].Start.x = mapX; MapPosTransitions[MapZoom3].Start.y = mapY; @@ -1299,24 +1300,24 @@ bool MapSystem::MapZoomInit3(int setMapX, int setMapY, int setMapSize, bool MapSystem::MapMoveAnimeInit2(int setMapX, int setMapY, int setTransitionSize) { MapMoveMode = 0; - if (!setTransitionSize || ScrWork[6362] < setTransitionSize) { - setTransitionSize = ScrWork[6362]; + if (!setTransitionSize || ScrWork[SW_MAP_ZOOM] < setTransitionSize) { + setTransitionSize = ScrWork[SW_MAP_ZOOM]; } - getMapPos(toFlt(ScrWork[6362]), toFlt(ScrWork[6363]), toFlt(ScrWork[6364]), - MapPosTransitions[MapMove2].Start.x, + getMapPos(toFlt(ScrWork[SW_MAP_ZOOM]), toFlt(ScrWork[SW_MAP_POS_X]), + toFlt(ScrWork[SW_MAP_POS_Y]), MapPosTransitions[MapMove2].Start.x, MapPosTransitions[MapMove2].Start.y, MapPosTransitions[MapMove2].Start.size, 0.5f, 0.5f); - getMapPos(toFlt(ScrWork[6362]), toFlt(setMapX), toFlt(setMapY), + getMapPos(toFlt(ScrWork[SW_MAP_ZOOM]), toFlt(setMapX), toFlt(setMapY), MapPosTransitions[MapMove2].End.x, MapPosTransitions[MapMove2].End.y, MapPosTransitions[MapMove2].End.size, 0.5, 0.5); - if (setTransitionSize == ScrWork[6362]) { + if (setTransitionSize == ScrWork[SW_MAP_ZOOM]) { MapMoveMode = 99; return MapZoomInit3(static_cast(MapPosTransitions[MapMove2].End.x), static_cast(MapPosTransitions[MapMove2].End.y), 0); } else { - float MapMoveCenterPosX = (setMapX + ScrWork[6363]) * 0.5f; - float MapMoveCenterPosY = (setMapY + ScrWork[6364]) * 0.5f; + float MapMoveCenterPosX = (setMapX + ScrWork[SW_MAP_POS_X]) * 0.5f; + float MapMoveCenterPosY = (setMapY + ScrWork[SW_MAP_POS_Y]) * 0.5f; float MapMoveLimitSize = 0; getMapPos(toFlt(setTransitionSize), MapMoveCenterPosX, MapMoveCenterPosY, MapMoveCenterPosX, MapMoveCenterPosY, MapMoveLimitSize, 0.5, 0.5); @@ -1853,19 +1854,20 @@ void MapSystem::MapPartsSort() { } void MapSystem::MapSetPos(float dt) { - float mapScaler = (ScrWork[6362] < 100) ? 100.0f - : (ScrWork[6362] > 800) ? 800.0f - : toFlt(ScrWork[6362]); + float mapScaler = (ScrWork[SW_MAP_ZOOM] < 100) ? 100.0f + : (ScrWork[SW_MAP_ZOOM] > 800) + ? 800.0f + : toFlt(ScrWork[SW_MAP_ZOOM]); float MapSheetWidth = MapBgSprite.Sheet.DesignWidth; float MapSheetHeight = MapBgSprite.Sheet.DesignHeight; MapBGWidth = MapBgSprite.Sheet.DesignWidth * 100.0f / mapScaler; MapBGHeight = MapBgSprite.Sheet.DesignHeight * 100.0f / mapScaler; - MapPosX = ScrWork[6363] - MapBGWidth / 2; - MapPosY = ScrWork[6364] - MapBGHeight / 2; + MapPosX = ScrWork[SW_MAP_POS_X] - MapBGWidth / 2; + MapPosY = ScrWork[SW_MAP_POS_Y] - MapBGHeight / 2; - if (!GetFlag(2805)) { + if (!GetFlag(SF_MAP_NO_CLAMP)) { if (MapPosX > MapSheetWidth - MapBGWidth) { MapPosX = MapSheetWidth - MapBGWidth; } @@ -1968,13 +1970,13 @@ void MapSystem::MapFadeMain(float dt) { } void MapSystem::Update(float dt) { - if (ScrWork[SW_MAP_ALPHA] && GetFlag(2800)) { + if (ScrWork[SW_MAP_ALPHA] && GetFlag(SF_MAPENABLE)) { MapSetPos(dt); } } void MapSystem::Render() { - if (!GetFlag(2800)) { + if (!GetFlag(SF_MAPENABLE)) { return; } // Render map bg diff --git a/src/games/cclcc/musicmenu.cpp b/src/games/cclcc/musicmenu.cpp index 714f26880..fd08a8805 100644 --- a/src/games/cclcc/musicmenu.cpp +++ b/src/games/cclcc/musicmenu.cpp @@ -8,6 +8,7 @@ #include "../../inputsystem.h" #include "../../text/text.h" #include "../../profile/dialogue.h" +#include "../../profile/vm.h" #include "../../data/savesystem.h" #include "../../audio/audiosystem.h" @@ -40,7 +41,9 @@ MusicTrackButton::MusicTrackButton(int id, int position, glm::vec2 pos) auto const lockedSc3Text = Vm::ScriptGetTextTableStrAddress( MusicStringTableId, MusicStringLockedIndex); Vm::Sc3VmThread dummy; - dummy.SetIp(lockedSc3Text); + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + dummy.IpOffset = lockedSc3Text.IpOffset; + dummy.ScriptBufferId = lockedSc3Text.BufferId; TextLayoutPlainLine(&dummy, LockedText, *Profile::Dialogue::DialogueFont, MusicTrackNameSize, {MusicButtonTextColor, MusicButtonTextOutlineColor}, 1.0f, @@ -165,6 +168,14 @@ void MusicMenu::Show() { } void MusicMenu::Init() { + switch (Profile::Vm::GameInstructionSet) { + case Vm::InstructionSet::LCCSwitch: { + ScrWork[SW_SYSSUBMENUCTMAX] = 32; + } break; + default: { + } + } + const auto musicOnclick = [this](Widgets::Button* target) { auto* musicBtn = static_cast(target); if (musicBtn->IsLocked) { diff --git a/src/games/cclcc/musicmenu.h b/src/games/cclcc/musicmenu.h index f783bcc08..515367a5b 100644 --- a/src/games/cclcc/musicmenu.h +++ b/src/games/cclcc/musicmenu.h @@ -32,7 +32,7 @@ class MusicTrackButton : public Widgets::Button { bool PrevFocusState = false; private: - std::array LockedText; + std::array LockedText; std::array NumberText; Widgets::Label ArtistName; }; diff --git a/src/games/cclcc/savesystem.cpp b/src/games/cclcc/savesystem.cpp index 0cc5c7e27..418fdd120 100644 --- a/src/games/cclcc/savesystem.cpp +++ b/src/games/cclcc/savesystem.cpp @@ -30,9 +30,9 @@ using namespace Impacto::Profile::ScriptVars; using namespace Impacto::Profile::Vm; using namespace Impacto::Profile::ConfigSystem; -uint32_t CalculateChecksum(std::span bufferData, - uint16_t initSum = 0, uint16_t initXor = 0, - bool swapSrcBytes = false) { +uint32_t SaveSystem::CalculateChecksum(std::span bufferData, + uint16_t initSum, uint16_t initXor, + bool swapSrcBytes) { // Initialize checksum variables uint32_t checksumSum = initSum; @@ -53,50 +53,15 @@ uint32_t CalculateChecksum(std::span bufferData, return result; } -SaveError SaveSystem::CheckSaveFile() const { - std::error_code ec; - - IoError existsState = Io::PathExists(SaveFilePath); - if (existsState == IoError_NotFound) { - return SaveError::NotFound; - } else if (existsState == IoError_Fail) { - ImpLog(LogLevel::Error, LogChannel::IO, - "Failed to check if save file exists, error: \"{:s}\"\n", - ec.message()); - return SaveError::Failed; - } - - auto saveFileSize = Io::GetFileSize(SaveFilePath); - if (saveFileSize == IoError_Fail) { - ImpLog(LogLevel::Error, LogChannel::IO, - "Failed to get save file size, error: \"{:s}\"\n", ec.message()); - return SaveError::Failed; - } else if (saveFileSize != SaveFileSize) { - return SaveError::Corrupted; - } - - Io::FilePermissionsFlags perms; - IoError permsState = Io::GetFilePermissions(SaveFilePath, perms); - using enum Io::FilePermissionsFlags; - if (permsState == IoError_Fail) { - ImpLog(LogLevel::Error, LogChannel::IO, - "Failed to get save file permissions, error: \"{:s}\"\n", - ec.message()); - return SaveError::Failed; - } else if ((perms & owner_read) == none || (perms & owner_write) == none) { - return SaveError::WrongUser; - } - - return SaveError::OK; -} - void SaveSystem::InitializeSystemData() { - std::fill(SystemData.begin(), SystemData.end(), 0x00); + auto& l = GetSaveLayout(); + auto systemData = GetSystemData(); + std::fill(systemData.begin(), systemData.end(), 0x00); Io::MemoryStream stream = - Io::MemoryStream(SystemData.data(), SystemData.size(), false); + Io::MemoryStream(systemData.data(), systemData.size(), false); - stream.Seek(0x8AC, SEEK_SET); + stream.Seek(l.ConfigOffset, SEEK_SET); Io::WriteLE(&stream, (Uint16)(Default::TextSpeed * 60)); Io::WriteLE(&stream, (Uint16)(Default::AutoSpeed * 60)); Io::WriteLE(&stream, (Uint8)(Default::GroupVolumes[Audio::ACG_Voice] * @@ -113,25 +78,21 @@ void SaveSystem::InitializeSystemData() { Io::WriteLE(&stream, Default::SyncVoice); Io::WriteLE(&stream, !Default::SkipRead); - stream.Seek(0x8BE, SEEK_SET); + stream.Seek(l.VoiceOffset, SEEK_SET); for (size_t i = 0; i < 33; i++) Io::WriteLE(&stream, !Default::VoiceMuted[i]); for (size_t i = 0; i < 33; i++) Io::WriteLE(&stream, (Uint8)(Default::VoiceVolume[i] * 128)); - stream.Seek(0x901, SEEK_SET); + stream.Seek(l.SkipVoiceOffset, SEEK_SET); Io::WriteLE(&stream, Default::SkipVoice); Io::WriteLE(&stream, Default::ShowTipsNotification); - stream.Seek(0x905, SEEK_SET); + stream.Seek(l.AdvanceTextOffset, SEEK_SET); Io::WriteLE(&stream, Default::AdvanceTextOnDirectionalInput); Io::WriteLE(&stream, Default::DirectionalInputForTrigger); Io::WriteLE(&stream, Default::TriggerStopSkip); - std::for_each_n(QuickSaveEntries, MaxSaveEntries, - [](auto& ptr) { ptr = new SaveFileEntry(); }); - std::for_each_n(FullSaveEntries, MaxSaveEntries, - [](auto& ptr) { ptr = new SaveFileEntry(); }); - WorkingSaveEntry = SaveFileEntry(); + InitSaveSlots(); const RectF viewport = Window->GetViewport(); WorkingSaveThumbnail.Sheet = SpriteSheet(viewport.Width, viewport.Height); @@ -146,6 +107,7 @@ void SaveSystem::InitializeSystemData() { void SaveSystem::LoadEntryBuffer(Io::MemoryStream& stream, SaveFileEntry& entry, SaveType saveType, Texture& tex) { + auto& l = GetSaveLayout(); entry.Status = Io::ReadLE(&stream); Io::ReadLE(&stream); uint16_t checksumSum = Io::ReadLE(&stream); @@ -177,20 +139,9 @@ void SaveSystem::LoadEntryBuffer(Io::MemoryStream& stream, SaveFileEntry& entry, entry.SaveType = Io::ReadLE(&stream); assert(stream.Position == 0x28); stream.Seek(0x58, SEEK_CUR); - Io::ReadArrayLE(entry.FlagWorkScript1.data(), &stream, - entry.FlagWorkScript1.size()); - assert(stream.Position == 178); - Io::ReadArrayLE(entry.FlagWorkScript2.data(), &stream, - entry.FlagWorkScript2.size()); - Io::ReadLE(&stream); - assert(stream.Position == 280); - Io::ReadArrayLE(entry.ScrWorkScript1.data(), &stream, - entry.ScrWorkScript1.size()); - assert(stream.Position == 2680); - Io::ReadArrayLE(entry.ScrWorkScript2.data(), &stream, - entry.ScrWorkScript2.size()); - - assert(stream.Position == 0x3958); + ReadWorkScriptData(stream, entry); + + assert(stream.Position == l.MainThreadOffset); entry.MainThreadExecPriority = Io::ReadLE(&stream); entry.MainThreadGroupId = Io::ReadLE(&stream); entry.MainThreadWaitCounter = Io::ReadLE(&stream); @@ -206,32 +157,33 @@ void SaveSystem::LoadEntryBuffer(Io::MemoryStream& stream, SaveFileEntry& entry, entry.MainThreadReturnBufIds[j] = Io::ReadLE(&stream); } Io::ReadLE(&stream); - assert(stream.Position == 0x39bc); + assert(stream.Position == l.MainThreadBufIdOffset); entry.MainThreadScriptBufferId = Io::ReadLE(&stream); Io::ReadArrayBE(entry.MainThreadVariables.data(), &stream, 16); entry.MainThreadDialoguePageId = Io::ReadLE(&stream); - assert(stream.Position == 0x3a04); - Io::ReadArrayLE(entry.WaveData.data(), &stream, entry.WaveData.size()); - assert(stream.Position == 0x3ec0); - Io::ReadArrayLE(entry.MapLoadData.data(), &stream, - entry.MapLoadData.size()); - Io::ReadArrayLE(entry.YesNoData.data(), &stream, - entry.YesNoData.size()); - + assert(stream.Position == l.WaveOffset); + ReadWaveData(stream, entry); + assert(stream.Position == l.MapLoadOffset); + Io::ReadArrayLE(entry.GetMapLoadData().data(), &stream, + entry.GetMapLoadData().size()); + Io::ReadArrayLE(entry.GetYesNoData().data(), &stream, + entry.GetYesNoData().size()); + + auto width = static_cast(l.ThumbnailWidth); + auto height = static_cast(l.ThumbnailHeight); Sprite& thumbnail = entry.SaveThumbnail; - thumbnail.Sheet = SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight); - thumbnail.Bounds = RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight); + thumbnail.Sheet = SpriteSheet(width, height); + thumbnail.Bounds = RectF(0.0f, 0.0f, width, height); - tex.Init(TexFmt_RGB, SaveThumbnailWidth, SaveThumbnailHeight); + tex.Init(TexFmt_RGB, l.ThumbnailWidth, l.ThumbnailHeight); - int thumbnailPadding = 0xA14; - stream.Seek(thumbnailPadding, SEEK_CUR); - Io::ReadArrayLE(entry.ThumbnailData.data(), &stream, - entry.ThumbnailData.size()); + stream.Seek(l.ThumbnailPadding, SEEK_CUR); + Io::ReadArrayLE(entry.GetThumbnailData().data(), &stream, + entry.GetThumbnailData().size()); - for (size_t i = 0; i < entry.ThumbnailData.size() / 2; i++) { - uint16_t pixel = - entry.ThumbnailData[i * 2] | (entry.ThumbnailData[i * 2 + 1] << 8); + for (size_t i = 0; i < entry.GetThumbnailData().size() / 2; i++) { + uint16_t pixel = entry.GetThumbnailData()[i * 2] | + (entry.GetThumbnailData()[i * 2 + 1] << 8); uint8_t r = (pixel & 0xF800) >> 8; uint8_t g = (uint8_t)((pixel & 0x07E0) >> 3); uint8_t b = (pixel & 0x001F) << 3; @@ -241,82 +193,13 @@ void SaveSystem::LoadEntryBuffer(Io::MemoryStream& stream, SaveFileEntry& entry, } } -SaveError SaveSystem::MountSaveFile(std::vector& textures) { - Io::Stream* stream; - IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream); - switch (err) { - case IoError_NotFound: - return SaveError::NotFound; - case IoError_Fail: - case IoError_Eof: - return SaveError::Corrupted; - case IoError_OK: - break; - }; - const RectF viewport = Window->GetViewport(); - WorkingSaveEntry = std::optional(SaveFileEntry()); - WorkingSaveThumbnail.Sheet = SpriteSheet(viewport.Width, viewport.Height); - WorkingSaveThumbnail.Bounds.SetSize(viewport.GetSize()); - - QueuedTexture txt{ - .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture), - }; - txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width, - (int)WorkingSaveThumbnail.Bounds.Height, 0x000000); - textures.push_back(txt); - - Io::ReadArrayLE(SystemData.data(), stream, SystemData.size()); - /* - uint32_t systemSaveChecksum = - CalculateChecksum(std::span(SystemData).subspan(4)); - */ - - int lockedQuickSaveSlots = 0; - textures.reserve(MaxSaveEntries * 2); - for (auto& entryArray : {FullSaveEntries, QuickSaveEntries}) { - SaveType saveType = - (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full; - [[maybe_unused]] int64_t saveDataPos = stream->Position; - for (int i = 0; i < MaxSaveEntries; i++) { - assert(stream->Position - saveDataPos == - static_cast(SaveEntrySize) * i); - entryArray[i] = new SaveFileEntry(); - - std::array entrySlotBuf; - Io::ReadArrayLE(entrySlotBuf.data(), stream, - entrySlotBuf.size()); - Io::MemoryStream saveEntryDataStream(entrySlotBuf.data(), - entrySlotBuf.size(), false); - - QueuedTexture tex{ - .Id = std::ref(entryArray[i]->SaveThumbnail.Sheet.Texture), - }; - LoadEntryBuffer(saveEntryDataStream, - static_cast(*entryArray[i]), saveType, - tex.Tex); - if (saveType == SaveType::Quick) { - lockedQuickSaveSlots += - static_cast(*entryArray[i]).Flags & WriteProtect; - } - textures.push_back(tex); - - // Todo, validate checksum? - } - } - SetLockedQuickSaveCount(lockedQuickSaveSlots); - SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries); - - delete stream; - return SaveError::OK; -} - void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) { auto* entry = GetSaveEntry(type, id); if (entry != nullptr && !(entry->Flags & WriteProtect)) { Renderer->FreeTexture(entry->SaveThumbnail.Sheet.Texture); uint8_t savedFlags = entry->Flags; - *entry = *WorkingSaveEntry; + UpdateWorkingSaveEntry(entry); entry->Flags = savedFlags; if (type == SaveType::Quick) { entry->SaveType = autoSaveType; @@ -326,13 +209,13 @@ void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, auto captureBuffer = Renderer->GetSpriteSheetImage(WorkingSaveThumbnail.Sheet); + auto& l = GetSaveLayout(); + auto width = static_cast(l.ThumbnailWidth); + auto height = static_cast(l.ThumbnailHeight); Texture tex; - tex.Init(TexFmt_RGBA, SaveThumbnailWidth, SaveThumbnailHeight); - - entry->SaveThumbnail.Sheet = - SpriteSheet(SaveThumbnailWidth, SaveThumbnailHeight); - entry->SaveThumbnail.Bounds = - RectF(0.0f, 0.0f, SaveThumbnailWidth, SaveThumbnailHeight); + tex.Init(TexFmt_RGBA, l.ThumbnailWidth, l.ThumbnailHeight); + entry->SaveThumbnail.Sheet = SpriteSheet(width, height); + entry->SaveThumbnail.Bounds = RectF(0.0f, 0.0f, width, height); int result = ResizeImage(WorkingSaveThumbnail.Bounds, entry->SaveThumbnail.Bounds, @@ -347,6 +230,7 @@ void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, void SaveSystem::SaveEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry, SaveType saveType) { + [[maybe_unused]] auto& l = GetSaveLayout(); Io::WriteLE(&memoryStream, entry.Status); Io::WriteLE(&memoryStream, entry.Checksum); Io::WriteLE(&memoryStream, 0); @@ -368,20 +252,8 @@ void SaveSystem::SaveEntryBuffer(Io::MemoryStream& memoryStream, Io::WriteLE(&memoryStream, entry.SaveType); assert(memoryStream.Position == 0x28); memoryStream.Seek(0x58, SEEK_CUR); - Io::WriteArrayLE(entry.FlagWorkScript1.data(), &memoryStream, - entry.FlagWorkScript1.size()); - assert(memoryStream.Position == 178); - Io::WriteArrayLE(entry.FlagWorkScript2.data(), &memoryStream, - entry.FlagWorkScript2.size()); - Io::WriteLE(&memoryStream, 0); - assert(memoryStream.Position == 280); - Io::WriteArrayLE(entry.ScrWorkScript1.data(), &memoryStream, - entry.ScrWorkScript1.size()); - assert(memoryStream.Position == 2680); - Io::WriteArrayLE(entry.ScrWorkScript2.data(), &memoryStream, - entry.ScrWorkScript2.size()); - - assert(memoryStream.Position == 0x3958); + WriteWorkScriptData(memoryStream, entry); + assert(memoryStream.Position == l.MainThreadOffset); Io::WriteLE(&memoryStream, entry.MainThreadExecPriority); Io::WriteLE(&memoryStream, entry.MainThreadGroupId); Io::WriteLE(&memoryStream, entry.MainThreadWaitCounter); @@ -397,56 +269,55 @@ void SaveSystem::SaveEntryBuffer(Io::MemoryStream& memoryStream, Io::WriteLE(&memoryStream, entry.MainThreadReturnBufIds[j]); } Io::WriteLE(&memoryStream, 0); - assert(memoryStream.Position == 0x39bc); + assert(memoryStream.Position == l.MainThreadBufIdOffset); Io::WriteLE(&memoryStream, entry.MainThreadScriptBufferId); Io::WriteArrayBE(entry.MainThreadVariables.data(), &memoryStream, 16); Io::WriteLE(&memoryStream, entry.MainThreadDialoguePageId); - assert(memoryStream.Position == 0x3a04); - Io::WriteArrayLE(entry.WaveData.data(), &memoryStream, - entry.WaveData.size()); - assert(memoryStream.Position == 0x3ec0); - Io::WriteArrayLE(entry.MapLoadData.data(), &memoryStream, - entry.MapLoadData.size()); - Io::WriteArrayLE(entry.YesNoData.data(), &memoryStream, - entry.YesNoData.size()); - - int thumbnailPadding = 0xA14; - memoryStream.Seek(thumbnailPadding, SEEK_CUR); - - Io::WriteArrayLE(entry.ThumbnailData.data(), &memoryStream, - entry.ThumbnailData.size()); + assert(memoryStream.Position == l.WaveOffset); + WriteWaveData(memoryStream, entry); + + assert(memoryStream.Position == l.MapLoadOffset); + Io::WriteArrayLE(entry.GetMapLoadData().data(), &memoryStream, + entry.GetMapLoadData().size()); + Io::WriteArrayLE(entry.GetYesNoData().data(), &memoryStream, + entry.GetYesNoData().size()); + + memoryStream.Seek(l.ThumbnailPadding, SEEK_CUR); + Io::WriteArrayLE(entry.GetThumbnailData().data(), &memoryStream, + entry.GetThumbnailData().size()); } void SaveSystem::SaveThumbnailData() { - std::vector thumbnailBuffer(SaveThumbnailSize * 2); + auto& l = GetSaveLayout(); + auto saveThumbnailSize = l.ThumbnailWidth * l.ThumbnailHeight * 2; + std::vector thumbnailBuffer(saveThumbnailSize * 2); for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) { for (int i = 0; i < MaxSaveEntries; i++) { - SaveFileEntry* entry = (SaveFileEntry*)entryArray[i]; + SaveFileEntry* entry = static_cast(entryArray[i]); if (entry->Status == 0) continue; Renderer->GetSpriteSheetImage(entry->SaveThumbnail.Sheet, thumbnailBuffer); - std::array& thumbnailData = - entry->ThumbnailData; - for (size_t j = 0; j < thumbnailBuffer.size(); j += 4) { uint8_t r = thumbnailBuffer[j]; uint8_t g = thumbnailBuffer[j + 1]; uint8_t b = thumbnailBuffer[j + 2]; uint16_t pixel = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); pixel = SDL_Swap16BE(pixel); - thumbnailData[j / 2] = pixel >> 8; - thumbnailData[j / 2 + 1] = pixel & 0xFF; + entry->GetThumbnailData()[j / 2] = pixel >> 8; + entry->GetThumbnailData()[j / 2 + 1] = pixel & 0xFF; } } } } SaveError SaveSystem::LoadSystemData() { + auto& l = GetSaveLayout(); + auto systemData = GetSystemData(); Io::MemoryStream stream = - Io::MemoryStream(SystemData.data(), SystemData.size(), false); + Io::MemoryStream(systemData.data(), systemData.size(), false); /* uint16_t systemSum = Io::ReadLE(&stream); @@ -455,14 +326,14 @@ SaveError SaveSystem::LoadSystemData() { stream.Seek(0x14, SEEK_SET); stream.Seek(0x80, SEEK_SET); - Io::ReadArrayLE(&FlagWork[100], &stream, 50); - Io::ReadArrayLE(&FlagWork[460], &stream, 40); + Io::ReadArrayLE(&FlagWork[l.SysFlagWorkDst1], &stream, 50); + Io::ReadArrayLE(&FlagWork[l.SysFlagWorkDst2], &stream, 40); stream.Seek(0xDC, SEEK_SET); - Io::ReadArrayLE(&ScrWork[1600], &stream, 400); + Io::ReadArrayLE(&ScrWork[l.SysScrWorkDst1], &stream, l.SysScrWorkLen1); Io::ReadArrayLE(&ScrWork[2000], &stream, 100); // Config settings - stream.Seek(0x8AC, SEEK_SET); + stream.Seek(l.ConfigOffset, SEEK_SET); TextSpeed = Io::ReadLE(&stream) / 60.0f; AutoSpeed = Io::ReadLE(&stream) / 60.0f; stream.Seek(1, SEEK_CUR); // VOICE2vol @@ -474,40 +345,31 @@ SaveError SaveSystem::LoadSystemData() { SyncVoice = Io::ReadLE(&stream); SkipRead = !Io::ReadLE(&stream); - stream.Seek(0x8BE, SEEK_SET); + stream.Seek(l.VoiceOffset, SEEK_SET); for (size_t i = 0; i < 33; i++) VoiceMuted[i] = !Io::ReadLE(&stream); for (size_t i = 0; i < 33; i++) VoiceVolume[i] = Io::ReadLE(&stream) / 128.0f; - stream.Seek(0x901, SEEK_SET); + stream.Seek(l.SkipVoiceOffset, SEEK_SET); SkipVoice = Io::ReadLE(&stream); ShowTipsNotification = Io::ReadLE(&stream); - stream.Seek(0x905, SEEK_SET); + stream.Seek(l.AdvanceTextOffset, SEEK_SET); AdvanceTextOnDirectionalInput = Io::ReadLE(&stream); DirectionalInputForTrigger = Io::ReadLE(&stream); TriggerStopSkip = Io::ReadLE(&stream); - stream.Seek(0xbce, SEEK_SET); - Io::ReadArrayLE(QuickSaveRecentSortedId.data(), &stream, - QuickSaveRecentSortedId.size()); - std::array sortedIdFreq{}; - - // backwards compat with older saves - for (uint8_t qsSlotId : QuickSaveRecentSortedId) { - if (sortedIdFreq[qsSlotId]++ > 1) { - std::iota(QuickSaveRecentSortedId.begin(), QuickSaveRecentSortedId.end(), - 0); - std::ranges::sort(QuickSaveRecentSortedId, - Impacto::SaveSystem::SaveRecencyComparator(), - [this](int id) { return *QuickSaveEntries[id]; }); - break; - } + if (l.QuickSortedIdIsLoaded) { + stream.Seek(l.QuickSortedIdOffset, SEEK_SET); + Io::ReadArrayLE(QuickSaveRecentSortedId.data(), &stream, + QuickSaveRecentSortedId.size()); } // EV Flags - stream.Seek(0xC0E, SEEK_SET); - for (int i = 0; i < 150; i++) { + stream.Seek(l.EVFlagsOffset, SEEK_SET); + auto EVFlags = GetEVFlags(); + size_t max = EVFlags.size() >> 3; + for (size_t i = 0; i < max; i++) { auto val = Io::ReadU8(&stream); EVFlags[8 * i] = val & 1; EVFlags[8 * i + 1] = (val & 2) != 0; @@ -519,35 +381,42 @@ SaveError SaveSystem::LoadSystemData() { EVFlags[8 * i + 7] = val >> 7; } - stream.Seek(0xCA4, SEEK_SET); - Io::ReadArrayLE(BGMFlags, &stream, 200); + stream.Seek(l.BgmFlagsOffset, SEEK_SET); + ReadBGMFlags(stream); - stream.Seek(0xd6c, SEEK_SET); - Io::ReadArrayLE(MessageFlags, &stream, 10000); + stream.Seek(l.MessageFlagsOffset, SEEK_SET); + auto messageFlags = GetMessageFlags(); + Io::ReadArrayLE(messageFlags.data(), &stream, messageFlags.size()); // EPnewList goes here - stream.Seek(0x347c, SEEK_SET); - Io::ReadArrayLE(GameExtraData, &stream, 1024); + stream.Seek(l.ExtraDataOffset, SEEK_SET); + auto gameExtraData = GetGameExtraData(); + Io::ReadArrayLE(gameExtraData.data(), &stream, gameExtraData.size()); return SaveError::OK; } void SaveSystem::SaveSystemData() { + auto& l = GetSaveLayout(); + auto systemData = GetSystemData(); Io::MemoryStream systemSaveStream = - Io::MemoryStream(SystemData.data(), SystemData.size(), false); + Io::MemoryStream(systemData.data(), systemData.size(), false); systemSaveStream.Seek(0x14, SEEK_SET); systemSaveStream.Seek(0x80, SEEK_SET); - Io::WriteArrayLE(&FlagWork[100], &systemSaveStream, 50); - Io::WriteArrayLE(&FlagWork[460], &systemSaveStream, 40); + Io::WriteArrayLE(&FlagWork[l.SysFlagWorkDst1], &systemSaveStream, + 50); + Io::WriteArrayLE(&FlagWork[l.SysFlagWorkDst2], &systemSaveStream, + 40); systemSaveStream.Seek(0xDC, SEEK_SET); - Io::WriteArrayLE(&ScrWork[1600], &systemSaveStream, 400); + Io::WriteArrayLE(&ScrWork[l.SysScrWorkDst1], &systemSaveStream, + l.SysScrWorkLen1); Io::WriteArrayLE(&ScrWork[2000], &systemSaveStream, 100); // Config settings - systemSaveStream.Seek(0x8AC, SEEK_SET); + systemSaveStream.Seek(l.ConfigOffset, SEEK_SET); Io::WriteLE(&systemSaveStream, (Uint16)(TextSpeed * 60)); Io::WriteLE(&systemSaveStream, (Uint16)(AutoSpeed * 60)); Io::WriteLE( @@ -568,28 +437,30 @@ void SaveSystem::SaveSystemData() { Io::WriteLE(&systemSaveStream, SyncVoice); Io::WriteLE(&systemSaveStream, !SkipRead); - systemSaveStream.Seek(0x8BE, SEEK_SET); + systemSaveStream.Seek(l.VoiceOffset, SEEK_SET); for (size_t i = 0; i < 33; i++) Io::WriteLE(&systemSaveStream, !VoiceMuted[i]); for (size_t i = 0; i < 33; i++) Io::WriteLE(&systemSaveStream, (Uint8)(VoiceVolume[i] * 128)); - systemSaveStream.Seek(0x901, SEEK_SET); + systemSaveStream.Seek(l.SkipVoiceOffset, SEEK_SET); Io::WriteLE(&systemSaveStream, SkipVoice); Io::WriteLE(&systemSaveStream, ShowTipsNotification); - systemSaveStream.Seek(0x905, SEEK_SET); + systemSaveStream.Seek(l.AdvanceTextOffset, SEEK_SET); Io::WriteLE(&systemSaveStream, AdvanceTextOnDirectionalInput); Io::WriteLE(&systemSaveStream, DirectionalInputForTrigger); Io::WriteLE(&systemSaveStream, TriggerStopSkip); - systemSaveStream.Seek(0xbce, SEEK_SET); + systemSaveStream.Seek(l.QuickSortedIdOffset, SEEK_SET); Io::WriteArrayLE(QuickSaveRecentSortedId.data(), &systemSaveStream, QuickSaveRecentSortedId.size()); // EV Flags - systemSaveStream.Seek(0xC0E, SEEK_SET); - for (int i = 0; i < 150; i++) { + systemSaveStream.Seek(l.EVFlagsOffset, SEEK_SET); + auto EVFlags = GetEVFlags(); + size_t max = EVFlags.size() >> 3; + for (size_t i = 0; i < max; i++) { const uint8_t evByte = (static_cast(EVFlags[8 * i])) | (static_cast(EVFlags[8 * i + 1]) << 1) | (static_cast(EVFlags[8 * i + 2]) << 2) | @@ -601,73 +472,20 @@ void SaveSystem::SaveSystemData() { Io::WriteLE(&systemSaveStream, evByte); } - systemSaveStream.Seek(0xCA4, SEEK_SET); - Io::WriteArrayLE(BGMFlags, &systemSaveStream, 200); + systemSaveStream.Seek(l.BgmFlagsOffset, SEEK_SET); + WriteBGMFlags(systemSaveStream); - systemSaveStream.Seek(0xd6c, SEEK_SET); - Io::WriteArrayLE(MessageFlags, &systemSaveStream, 10000); + systemSaveStream.Seek(l.MessageFlagsOffset, SEEK_SET); + auto messageFlags = GetMessageFlags(); + Io::WriteArrayLE(messageFlags.data(), &systemSaveStream, + messageFlags.size()); // EPnewList goes here - systemSaveStream.Seek(0x347c, SEEK_SET); - Io::WriteArrayLE(GameExtraData, &systemSaveStream, 1024); -} - -SaveError SaveSystem::WriteSaveFile() { - using CF = Io::PhysicalFileStream::CreateFlagsMode; - Io::Stream* stream; - IoError err = Io::PhysicalFileStream::Create( - SaveFilePath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE); - if (err != IoError_OK) { - ImpLog(LogLevel::Error, LogChannel::IO, - "Failed to open save file for writing\n"); - return SaveError::Failed; - } - - stream->Seek(0, SEEK_SET); - Io::MemoryStream systemSaveStream = - Io::MemoryStream(SystemData.data(), SystemData.size(), false); - uint32_t systemChecksum = CalculateChecksum(std::span(SystemData).subspan(4)); - systemSaveStream.Seek(0, SEEK_SET); - Io::WriteLE(&systemSaveStream, systemChecksum >> 16); - Io::WriteLE(&systemSaveStream, systemChecksum & 0xFFFF); - Io::WriteArrayLE(SystemData.data(), stream, SystemData.size()); - // End system data - for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) { - SaveType saveType = - (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full; - [[maybe_unused]] int64_t saveDataPos = stream->Position; - for (int i = 0; i < MaxSaveEntries; i++) { - SaveFileEntry* entry = (SaveFileEntry*)entryArray[i]; - if (entry == nullptr || entry->Status == 0) { - Io::WriteLE(stream, 0, SaveEntrySize); - } else { - assert(stream->Position - saveDataPos == - static_cast(SaveEntrySize) * i); - std::array entrySlotBuf{}; - Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(), - entrySlotBuf.size(), false); - SaveEntryBuffer(saveEntryMemoryStream, *entry, saveType); - uint32_t entryCheckSum = CalculateChecksum( - std::span(entrySlotBuf).subspan(6, 23029 * 2), 18198, 5250, false); - saveEntryMemoryStream.Seek(2, SEEK_SET); - Io::WriteLE(&saveEntryMemoryStream, entryCheckSum >> 16); - Io::WriteLE(&saveEntryMemoryStream, entryCheckSum & 0xFFFF); - Io::WriteArrayLE(entrySlotBuf.data(), stream, - entrySlotBuf.size()); - } - } - } - delete stream; - return SaveError::OK; -} - -uint32_t SaveSystem::GetSavePlayTime(SaveType type, int id) const { - return GetSaveEntry(type, id)->PlayTime; -} - -uint8_t SaveSystem::GetSaveFlags(SaveType type, int id) const { - return GetSaveEntry(type, id)->Flags; + systemSaveStream.Seek(l.ExtraDataOffset, SEEK_SET); + auto gameExtraData = GetGameExtraData(); + Io::WriteArrayLE(gameExtraData.data(), &systemSaveStream, + gameExtraData.size()); } void SaveSystem::SetSaveFlags(SaveType type, int id, uint8_t flags) { @@ -692,8 +510,18 @@ tm const& SaveSystem::GetSaveDate(SaveType type, int id) const { return GetSaveEntry(type, id)->SaveDate; } +uint32_t SaveSystem::GetSavePlayTime(SaveType type, int id) const { + return GetSaveEntry(type, id)->PlayTime; +} + +uint8_t SaveSystem::GetSaveFlags(SaveType type, int id) const { + return GetSaveEntry(type, id)->Flags; +} + void SaveSystem::SaveMemory() { if (WorkingSaveEntry) { + auto& l = GetSaveLayout(); + WorkingSaveEntry->Status = 1; const tm timeinfo = CurrentDateTime(); @@ -701,18 +529,22 @@ void SaveSystem::SaveMemory() { WorkingSaveEntry->PlayTime = ScrWork[SW_PLAYTIME]; WorkingSaveEntry->SwTitle = ScrWork[SW_TITLE]; - std::copy(FlagWork.begin() + 50, - FlagWork.begin() + 50 + WorkingSaveEntry->FlagWorkScript1.size(), - WorkingSaveEntry->FlagWorkScript1.begin()); - std::copy(FlagWork.begin() + 300, - FlagWork.begin() + 300 + WorkingSaveEntry->FlagWorkScript2.size(), - WorkingSaveEntry->FlagWorkScript2.begin()); - std::copy(ScrWork.begin() + 1000, - ScrWork.begin() + 1000 + WorkingSaveEntry->ScrWorkScript1.size(), - WorkingSaveEntry->ScrWorkScript1.begin()); - std::copy(ScrWork.begin() + 4300, - ScrWork.begin() + 4300 + WorkingSaveEntry->ScrWorkScript2.size(), - WorkingSaveEntry->ScrWorkScript2.begin()); + std::copy( + FlagWork.begin() + 50, + FlagWork.begin() + 50 + WorkingSaveEntry->GetFlagWorkScript1().size(), + WorkingSaveEntry->GetFlagWorkScript1().begin()); + std::copy(FlagWork.begin() + l.FlagWork2Src, + FlagWork.begin() + l.FlagWork2Src + + WorkingSaveEntry->GetFlagWorkScript2().size(), + WorkingSaveEntry->GetFlagWorkScript2().begin()); + std::copy( + ScrWork.begin() + 1000, + ScrWork.begin() + 1000 + WorkingSaveEntry->GetScrWorkScript1().size(), + WorkingSaveEntry->GetScrWorkScript1().begin()); + std::copy( + ScrWork.begin() + 4300, + ScrWork.begin() + 4300 + WorkingSaveEntry->GetScrWorkScript2().size(), + WorkingSaveEntry->GetScrWorkScript2().begin()); int threadId = ScrWork[SW_MAINTHDP]; Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF]; @@ -734,20 +566,11 @@ void SaveSystem::SaveMemory() { WorkingSaveEntry->MainThreadDialoguePageId = thd->DialoguePageId; } UI::CCLCC::MapSystem::GetInstance().MapSave( - WorkingSaveEntry->MapLoadData.data()); + WorkingSaveEntry->GetMapLoadData().data()); UI::CCLCC::YesNoTrigger::GetInstance().Save( - WorkingSaveEntry->YesNoData.data()); - WaveSave(std::span(WorkingSaveEntry->WaveData)); - } -} - -void SaveSystem::LoadEntry(SaveType type, int id) { - if (!WorkingSaveEntry) { - ImpLog(LogLevel::Error, LogChannel::IO, - "Failed to load save memory: no working save\n"); - return; + WorkingSaveEntry->GetYesNoData().data()); + WaveSave(WorkingSaveEntry->GetWaveData()); } - WorkingSaveEntry = *GetSaveEntry(type, id); } void SaveSystem::LoadMemoryNew(LoadProcess load) { @@ -757,36 +580,28 @@ void SaveSystem::LoadMemoryNew(LoadProcess load) { return; } if (load == LoadProcess::Vars) { + auto& l = GetSaveLayout(); + ScrWork[SW_PLAYTIME] = WorkingSaveEntry->PlayTime; ScrWork[SW_TITLE] = WorkingSaveEntry->SwTitle; ScrWork[SW_AUTOSAVERESTART] = WorkingSaveEntry->SaveType; - std::ranges::copy(WorkingSaveEntry->FlagWorkScript1, FlagWork.begin() + 50); - std::ranges::copy(WorkingSaveEntry->FlagWorkScript2, - FlagWork.begin() + 300); - std::ranges::copy(WorkingSaveEntry->ScrWorkScript1, ScrWork.begin() + 1000); - std::ranges::copy(WorkingSaveEntry->ScrWorkScript2, ScrWork.begin() + 4300); + std::ranges::copy(WorkingSaveEntry->GetFlagWorkScript1(), + FlagWork.begin() + 50); + std::ranges::copy(WorkingSaveEntry->GetFlagWorkScript2(), + FlagWork.begin() + l.FlagWork2Src); + std::ranges::copy(WorkingSaveEntry->GetScrWorkScript1(), + ScrWork.begin() + 1000); + std::ranges::copy(WorkingSaveEntry->GetScrWorkScript2(), + ScrWork.begin() + 4300); UI::CCLCC::MapSystem::GetInstance().MapLoad( - WorkingSaveEntry->MapLoadData.data()); + WorkingSaveEntry->GetMapLoadData().data()); UI::CCLCC::YesNoTrigger::GetInstance().Load( - WorkingSaveEntry->YesNoData.data()); - WaveLoad(std::span(WorkingSaveEntry->WaveData)); - - // TODO: What to do about this mess I wonder... - ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO]; - ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1]; - ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2]; - ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO]; - ScrWork[SW_SVBGM2NO] = ScrWork[SW_BGMREQNO2]; - ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2]; - ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3]; - ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4]; - ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5]; - for (int i = 0; i < 8; i++) { - ScrWork[SW_SVBGNO1 + i] = ScrWork[SW_BG1NO + i * ScrWorkBgStructSize]; - ScrWork[SW_SVCHANO1 + i] = ScrWork[SW_CHA1NO + i * ScrWorkChaStructSize]; - } + WorkingSaveEntry->GetYesNoData().data()); + WaveLoad(WorkingSaveEntry->GetWaveData()); + + LoadScrWork(); } else if (ScrWork[SW_MAINTHDP] != 0) { int threadId = ScrWork[SW_MAINTHDP]; Sc3VmThread* thd = &ThreadPool[threadId & 0x7FFFFFFF]; @@ -824,50 +639,55 @@ int SaveSystem::GetSaveTitle(SaveType type, int id) const { } uint32_t SaveSystem::GetTipStatus(size_t tipId) const { + auto gameExtraData = GetGameExtraData(); tipId *= 3; - uint8_t lockStatus = (GameExtraData[tipId >> 3] & Flbit[tipId & 7]) != 0; + uint8_t lockStatus = (gameExtraData[tipId >> 3] & Flbit[tipId & 7]) != 0; uint8_t newStatus = - (GameExtraData[(tipId + 1) >> 3] & Flbit[(tipId + 1) & 7]) != 0; + (gameExtraData[(tipId + 1) >> 3] & Flbit[(tipId + 1) & 7]) != 0; uint8_t unreadStatus = - (GameExtraData[(tipId + 2) >> 3] & Flbit[(tipId + 2) & 7]) != 0; + (gameExtraData[(tipId + 2) >> 3] & Flbit[(tipId + 2) & 7]) != 0; return (lockStatus | (unreadStatus << 1)) | (newStatus << 2); } void SaveSystem::SetTipStatus(size_t tipId, bool isLocked, bool isUnread, bool isNew) { + auto gameExtraData = GetGameExtraData(); tipId *= 3; if (isLocked) { - GameExtraData[tipId >> 3] &= ~(Flbit[tipId & 7]); + gameExtraData[tipId >> 3] &= ~(Flbit[tipId & 7]); } else { - GameExtraData[tipId >> 3] |= Flbit[tipId & 7]; + gameExtraData[tipId >> 3] |= Flbit[tipId & 7]; } if (isUnread) { - GameExtraData[(tipId + 2) >> 3] &= ~(Flbit[(tipId + 2) & 7]); + gameExtraData[(tipId + 2) >> 3] &= ~(Flbit[(tipId + 2) & 7]); } else { - GameExtraData[(tipId + 2) >> 3] |= Flbit[(tipId + 2) & 7]; + gameExtraData[(tipId + 2) >> 3] |= Flbit[(tipId + 2) & 7]; } if (isNew) { - GameExtraData[(tipId + 1) >> 3] &= ~(Flbit[(tipId + 1) & 7]); + gameExtraData[(tipId + 1) >> 3] &= ~(Flbit[(tipId + 1) & 7]); } else { - GameExtraData[(tipId + 1) >> 3] |= Flbit[(tipId + 1) & 7]; + gameExtraData[(tipId + 1) >> 3] |= Flbit[(tipId + 1) & 7]; } } void SaveSystem::SetLineRead(const size_t scriptId, const size_t lineId) { const std::optional offset = GetLineBitOffset(scriptId, lineId); + if (!offset.has_value()) return; + auto messageFlags = GetMessageFlags(); // TODO: update some ScrWorks (2003, 2005 & 2006) - MessageFlags[*offset >> 3] |= Flbit[*offset & 0b111]; + messageFlags[*offset >> 3] |= Flbit[*offset & 0b111]; } bool SaveSystem::IsLineRead(const size_t scriptId, const size_t lineId) const { const std::optional offset = GetLineBitOffset(scriptId, lineId); if (!offset.has_value()) return false; + auto messageFlags = GetMessageFlags(); const uint8_t flbit = Flbit[*offset & 0b111]; - const uint8_t viewed = MessageFlags[*offset >> 3]; + const uint8_t viewed = messageFlags[*offset >> 3]; return static_cast(flbit & viewed); } @@ -889,6 +709,7 @@ void SaveSystem::GetReadMessagesCount(int* totalMessageCount, void SaveSystem::GetViewedEVsCount(int* totalEVCount, int* viewedEVCount) const { + auto EVFlags = GetEVFlags(); for (int i = 0; i < MaxAlbumEntries; i++) { if (AlbumEvData[i][0] == 0xFFFF) break; for (int j = 0; j < MaxAlbumSubEntries; j++) { @@ -900,6 +721,7 @@ void SaveSystem::GetViewedEVsCount(int* totalEVCount, } void SaveSystem::GetEVStatus(int evId, int* totalVariations, int* viewedVariations) const { + auto EVFlags = GetEVFlags(); *totalVariations = 0; *viewedVariations = 0; for (int i = 0; i < MaxAlbumSubEntries; i++) { @@ -909,16 +731,27 @@ void SaveSystem::GetEVStatus(int evId, int* totalVariations, } } -void SaveSystem::SetEVStatus(int id) { EVFlags[id] = true; } +void SaveSystem::SetEVStatus(int id) { + auto EVFlags = GetEVFlags(); + EVFlags[id] = true; +} bool SaveSystem::GetEVVariationIsUnlocked(size_t evId, size_t variationIdx) const { if (AlbumEvData[evId][variationIdx] == 0xFFFF) return false; + auto EVFlags = GetEVFlags(); + return EVFlags[AlbumEvData[evId][variationIdx]]; } -bool SaveSystem::GetBgmFlag(int id) const { return BGMFlags[id]; } -void SaveSystem::SetBgmFlag(int id, bool flag) { BGMFlags[id] = flag; } +bool SaveSystem::GetBgmFlag(int id) const { + auto BGMFlags = GetBGMFlags(); + return BGMFlags[id]; +} +void SaveSystem::SetBgmFlag(int id, bool flag) { + auto BGMFlags = GetBGMFlags(); + BGMFlags[id] = flag; +} void SaveSystem::SetCheckpointId(int id) { if (WorkingSaveEntry) WorkingSaveEntry->MainThreadIp = id; diff --git a/src/games/cclcc/savesystem.h b/src/games/cclcc/savesystem.h index 3bfeb6eec..54ce396c6 100644 --- a/src/games/cclcc/savesystem.h +++ b/src/games/cclcc/savesystem.h @@ -11,32 +11,106 @@ namespace CCLCC { using namespace Impacto::SaveSystem; -constexpr size_t SaveEntrySize = 0x1b110; -constexpr int SaveFileSize = SaveEntrySize * MaxSaveEntries * 2 + 0x387c; - -constexpr int SaveThumbnailWidth = 240; -constexpr int SaveThumbnailHeight = 135; -// CCLCC PS4 Save thumbnails are 240x135 RGB16 -constexpr int SaveThumbnailSize = - SaveThumbnailWidth * SaveThumbnailHeight * 4 / 2; - -class SaveFileEntry : public SaveFileEntryBase { +struct SaveLayout { + size_t ConfigOffset; + size_t VoiceOffset; + size_t SkipVoiceOffset; + size_t AdvanceTextOffset; + size_t QuickSortedIdOffset; + bool QuickSortedIdIsLoaded; + size_t EVFlagsOffset; + size_t BgmFlagsOffset; + size_t MessageFlagsOffset; + size_t ExtraDataOffset; + + size_t SysFlagWorkDst1; + size_t SysFlagWorkDst2; + size_t SysScrWorkDst1; + size_t SysScrWorkLen1; + + int64_t FlagWork2Src; + int64_t MainThreadOffset; + int64_t MainThreadBufIdOffset; + int64_t WaveOffset; + int64_t MapLoadOffset; + int64_t ThumbnailPadding; + + int ThumbnailWidth, ThumbnailHeight; +}; +struct SaveFileEntry : SaveFileEntryBase { public: - std::array FlagWorkScript1; // 50 bytes from &FlagWork[50] - std::array FlagWorkScript2; // 100 bytes from &FlagWork[300] - std::array ScrWorkScript1; // 2400 bytes from &ScrWork[1000] - std::array ScrWorkScript2; // 12000 bytes from &ScrWork[4300] - std::array MapLoadData; - std::array YesNoData; - std::array - WaveData; // 3 wave types * 20 waves * 5 fields + 3 counts - std::array ThumbnailData; + std::span GetFlagWorkScript1() { + return std::bit_cast>( + std::as_const(*this).GetFlagWorkScript1()); + } + virtual std::span GetFlagWorkScript1() const = 0; + + std::span GetFlagWorkScript2() { + return std::bit_cast>( + std::as_const(*this).GetFlagWorkScript2()); + } + virtual std::span GetFlagWorkScript2() const = 0; + + std::span GetScrWorkScript1() { + return std::bit_cast>( + std::as_const(*this).GetScrWorkScript1()); + } + virtual std::span GetScrWorkScript1() const = 0; + + std::span GetScrWorkScript2() { + return std::bit_cast>( + std::as_const(*this).GetScrWorkScript2()); + } + virtual std::span GetScrWorkScript2() const = 0; + + std::span GetMapLoadData() { + return std::bit_cast>( + std::as_const(*this).GetMapLoadData()); + } + virtual std::span GetMapLoadData() const = 0; + + std::span GetYesNoData() { + return std::bit_cast>( + std::as_const(*this).GetYesNoData()); + } + virtual std::span GetYesNoData() const = 0; + + std::span GetWaveData() { + return std::bit_cast>(std::as_const(*this).GetWaveData()); + } + virtual std::span GetWaveData() const = 0; + + std::span GetThumbnailData() { + return std::bit_cast>( + std::as_const(*this).GetThumbnailData()); + } + virtual std::span GetThumbnailData() const = 0; + + virtual ~SaveFileEntry() = default; }; class SaveSystem : public SaveSystemBase { public: - SaveError CheckSaveFile() const override; - SaveError MountSaveFile(std::vector& textures) override; + uint32_t static CalculateChecksum(std::span bufferData, + uint16_t initSum = 0, uint16_t initXor = 0, + bool swapSrcBytes = false); + + // Flags and Scr + virtual void WriteWorkScriptData(Io::MemoryStream& stream, + SaveFileEntry& entry) = 0; + virtual void ReadWorkScriptData(Io::MemoryStream& stream, + SaveFileEntry& entry) = 0; + virtual void WriteWaveData(Io::MemoryStream& stream, + SaveFileEntry& entry) = 0; + virtual void ReadWaveData(Io::MemoryStream& stream, SaveFileEntry& entry) = 0; + + virtual void WriteBGMFlags(Io::MemoryStream& stream) = 0; + virtual void ReadBGMFlags(Io::MemoryStream& stream) = 0; + + virtual void LoadScrWork() = 0; + + virtual const SaveLayout& GetSaveLayout() const = 0; + virtual void InitSaveSlots() = 0; SaveError LoadSystemData() override; void SaveSystemData() override; @@ -49,13 +123,10 @@ class SaveSystem : public SaveSystemBase { SaveType saveType, Texture& tex); void SaveEntryBuffer(Io::MemoryStream& memoryStream, SaveFileEntry& entry, SaveType saveType); - void LoadEntry(SaveType type, int id) override; void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override; void SaveMemory() override; void LoadMemoryNew(LoadProcess load) override; - - SaveError WriteSaveFile() override; uint32_t GetSavePlayTime(SaveType type, int id) const override; uint8_t GetSaveFlags(SaveType type, int id) const override; void SetSaveFlags(SaveType type, int id, uint8_t flags) override; @@ -87,13 +158,20 @@ class SaveSystem : public SaveSystemBase { void WaveSave(std::span data); void WaveLoad(std::span data) const; - private: - uint8_t GameExtraData[1024]; - uint8_t MessageFlags[10000]; - std::array SystemData; - bool EVFlags[1200]; - uint8_t BGMFlags[200]; - std::optional WorkingSaveEntry; + virtual void UpdateWorkingSaveEntry(SaveFileEntry* entry) = 0; + + protected: + virtual std::span GetGameExtraData() = 0; + virtual std::span GetGameExtraData() const = 0; + virtual std::span GetMessageFlags() = 0; + virtual std::span GetMessageFlags() const = 0; + virtual std::span GetSystemData() = 0; + virtual std::span GetEVFlags() = 0; + virtual std::span GetEVFlags() const = 0; + virtual std::span GetBGMFlags() = 0; + virtual std::span GetBGMFlags() const = 0; + + std::unique_ptr WorkingSaveEntry; }; } // namespace CCLCC diff --git a/src/games/cclcc/savesystem_ps4.cpp b/src/games/cclcc/savesystem_ps4.cpp new file mode 100644 index 000000000..3850ca978 --- /dev/null +++ b/src/games/cclcc/savesystem_ps4.cpp @@ -0,0 +1,312 @@ +#include "savesystem_ps4.h" + +#include "../../io/stream.h" +#include "../../io/physicalfilestream.h" +#include "../../mem.h" +#include "../../vm/vm.h" +#include "../../profile/data/savesystem.h" +#include "../../profile/scriptvars.h" +#include "../../profile/vm.h" +#include "../../renderer/renderer.h" +#include "../../profile/configsystem.h" +#include "../../effects/wave.h" +#include "../../audio/audiosystem.h" + +#include "mapsystem.h" +#include "yesnotrigger.h" + +#include +#include +#include +#include + +namespace Impacto { +namespace CCLCC_PS4 { + +using namespace Impacto::Vm; +using namespace Impacto::Effects; +using namespace Impacto::Profile::SaveSystem; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Profile::Vm; +using namespace Impacto::Profile::ConfigSystem; + +static constexpr CCLCC::SaveLayout Ps4Layout{ + .ConfigOffset = 0x8AC, + .VoiceOffset = 0x8BE, + .SkipVoiceOffset = 0x901, + .AdvanceTextOffset = 0x905, + .QuickSortedIdOffset = 0xBCE, + .QuickSortedIdIsLoaded = true, + .EVFlagsOffset = 0xC0E, + .BgmFlagsOffset = 0xCA4, + .MessageFlagsOffset = 0xD6C, + .ExtraDataOffset = 0x347C, + + .SysFlagWorkDst1 = 100, + .SysFlagWorkDst2 = 460, + .SysScrWorkDst1 = 1600, + .SysScrWorkLen1 = 400, + .FlagWork2Src = 300, + + .MainThreadOffset = 0x3958, + .MainThreadBufIdOffset = 0x39bc, + .WaveOffset = 0x3A04, + .MapLoadOffset = 0x3EC0, + .ThumbnailPadding = 0xA14, + + .ThumbnailWidth = 240, + .ThumbnailHeight = 135, +}; + +SaveError SaveSystem::CheckSaveFile() const { + std::error_code ec; + + IoError existsState = Io::PathExists(SaveFilePath); + if (existsState == IoError_NotFound) { + return SaveError::NotFound; + } else if (existsState == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to check if save file exists, error: \"{:s}\"\n", + ec.message()); + return SaveError::Failed; + } + + auto saveFileSize = Io::GetFileSize(SaveFilePath); + if (saveFileSize == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to get save file size, error: \"{:s}\"\n", ec.message()); + return SaveError::Failed; + } else if (saveFileSize != SaveFileSize) { + return SaveError::Corrupted; + } + + Io::FilePermissionsFlags perms; + IoError permsState = Io::GetFilePermissions(SaveFilePath, perms); + using enum Io::FilePermissionsFlags; + if (permsState == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to get save file permissions, error: \"{:s}\"\n", + ec.message()); + return SaveError::Failed; + } else if ((perms & owner_read) == none || (perms & owner_write) == none) { + return SaveError::WrongUser; + } + + return SaveError::OK; +} + +void SaveSystem::LoadEntry(SaveType type, int id) { + if (!WorkingSaveEntry) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to load save memory: no working save\n"); + return; + } + WorkingSaveEntry = + std::make_unique(*GetSaveEntry(type, id)); +} + +SaveError SaveSystem::MountSaveFile(std::vector& textures) { + Io::Stream* stream; + IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream); + switch (err) { + case IoError_NotFound: + return SaveError::NotFound; + case IoError_Fail: + case IoError_Eof: + return SaveError::Corrupted; + case IoError_OK: + break; + }; + const RectF viewport = Window->GetViewport(); + WorkingSaveEntry = std::make_unique(SaveFileEntry{}); + WorkingSaveThumbnail.Sheet = SpriteSheet(viewport.Width, viewport.Height); + WorkingSaveThumbnail.Bounds.SetSize(viewport.GetSize()); + + QueuedTexture txt{ + .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture), + }; + txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width, + (int)WorkingSaveThumbnail.Bounds.Height, 0x000000); + textures.push_back(txt); + + Io::ReadArrayLE(SystemData.data(), stream, SystemData.size()); + /* + uint32_t systemSaveChecksum = + CalculateChecksum(std::span(SystemData).subspan(4)); + */ + + int lockedQuickSaveSlots = 0; + textures.reserve(MaxSaveEntries * 2); + for (auto& entryArray : {FullSaveEntries, QuickSaveEntries}) { + SaveType saveType = + (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full; + [[maybe_unused]] int64_t saveDataPos = stream->Position; + for (int i = 0; i < MaxSaveEntries; i++) { + assert(stream->Position - saveDataPos == + static_cast(SaveEntrySize) * i); + entryArray[i] = new SaveFileEntry(); + + std::array entrySlotBuf; + Io::ReadArrayLE(entrySlotBuf.data(), stream, + entrySlotBuf.size()); + Io::MemoryStream saveEntryDataStream(entrySlotBuf.data(), + entrySlotBuf.size(), false); + + QueuedTexture tex{ + .Id = std::ref(entryArray[i]->SaveThumbnail.Sheet.Texture), + }; + LoadEntryBuffer(saveEntryDataStream, + static_cast(*entryArray[i]), saveType, + tex.Tex); + if (saveType == SaveType::Quick) { + lockedQuickSaveSlots += + static_cast(*entryArray[i]).Flags & WriteProtect; + } + textures.push_back(tex); + + // Todo, validate checksum? + } + } + SetLockedQuickSaveCount(lockedQuickSaveSlots); + SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries); + + delete stream; + return SaveError::OK; +} + +SaveError SaveSystem::WriteSaveFile() { + using CF = Io::PhysicalFileStream::CreateFlagsMode; + Io::Stream* stream; + IoError err = Io::PhysicalFileStream::Create( + SaveFilePath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE); + if (err != IoError_OK) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to open save file for writing\n"); + return SaveError::Failed; + } + + stream->Seek(0, SEEK_SET); + Io::MemoryStream systemSaveStream = + Io::MemoryStream(SystemData.data(), SystemData.size(), false); + uint32_t systemChecksum = CalculateChecksum(std::span(SystemData).subspan(4)); + systemSaveStream.Seek(0, SEEK_SET); + Io::WriteLE(&systemSaveStream, systemChecksum >> 16); + Io::WriteLE(&systemSaveStream, systemChecksum & 0xFFFF); + Io::WriteArrayLE(SystemData.data(), stream, SystemData.size()); + // End system data + for (auto* entryArray : {FullSaveEntries, QuickSaveEntries}) { + SaveType saveType = + (entryArray == QuickSaveEntries) ? SaveType::Quick : SaveType::Full; + [[maybe_unused]] int64_t saveDataPos = stream->Position; + for (int i = 0; i < MaxSaveEntries; i++) { + SaveFileEntry* entry = (SaveFileEntry*)entryArray[i]; + if (entry == nullptr || entry->Status == 0) { + Io::WriteLE(stream, 0, SaveEntrySize); + } else { + assert(stream->Position - saveDataPos == + static_cast(SaveEntrySize) * i); + std::array entrySlotBuf{}; + Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(), + entrySlotBuf.size(), false); + SaveEntryBuffer(saveEntryMemoryStream, *entry, saveType); + uint32_t entryCheckSum = CalculateChecksum( + std::span(entrySlotBuf).subspan(6, 23029 * 2), 18198, 5250, false); + saveEntryMemoryStream.Seek(2, SEEK_SET); + Io::WriteLE(&saveEntryMemoryStream, entryCheckSum >> 16); + Io::WriteLE(&saveEntryMemoryStream, entryCheckSum & 0xFFFF); + Io::WriteArrayLE(entrySlotBuf.data(), stream, + entrySlotBuf.size()); + } + } + } + delete stream; + return SaveError::OK; +} + +const CCLCC::SaveLayout& SaveSystem::GetSaveLayout() const { return Ps4Layout; } + +void SaveSystem::InitSaveSlots() { + std::for_each_n(QuickSaveEntries, MaxSaveEntries, + [](auto& ptr) { ptr = new SaveFileEntry(); }); + std::for_each_n(FullSaveEntries, MaxSaveEntries, + [](auto& ptr) { ptr = new SaveFileEntry(); }); + WorkingSaveEntry = std::make_unique(SaveFileEntry{}); +} + +void SaveSystem::ReadWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::ReadArrayLE(localEntry->FlagWorkScript1.data(), &stream, + localEntry->FlagWorkScript1.size()); + assert(stream.Position == 178); + Io::ReadArrayLE(localEntry->FlagWorkScript2.data(), &stream, + localEntry->FlagWorkScript2.size()); + Io::ReadLE(&stream); + assert(stream.Position == 280); + Io::ReadArrayLE(localEntry->ScrWorkScript1.data(), &stream, + localEntry->ScrWorkScript1.size()); + assert(stream.Position == 2680); + Io::ReadArrayLE(localEntry->ScrWorkScript2.data(), &stream, + localEntry->ScrWorkScript2.size()); +} + +void SaveSystem::WriteWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::WriteArrayLE(localEntry->FlagWorkScript1.data(), &stream, + localEntry->FlagWorkScript1.size()); + assert(stream.Position == 178); + Io::WriteArrayLE(localEntry->FlagWorkScript2.data(), &stream, + localEntry->FlagWorkScript2.size()); + Io::WriteLE(&stream, 0); + assert(stream.Position == 280); + Io::WriteArrayLE(localEntry->ScrWorkScript1.data(), &stream, + localEntry->ScrWorkScript1.size()); + assert(stream.Position == 2680); + Io::WriteArrayLE(localEntry->ScrWorkScript2.data(), &stream, + localEntry->ScrWorkScript2.size()); +} + +void SaveSystem::WriteWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::WriteArrayLE(localEntry->WaveData.data(), &stream, + localEntry->WaveData.size()); +}; + +void SaveSystem::ReadWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::ReadArrayLE(localEntry->WaveData.data(), &stream, + localEntry->WaveData.size()); +}; + +void SaveSystem::WriteBGMFlags(Io::MemoryStream& stream) { + Io::WriteArrayLE(BGMFlags.data(), &stream, 200); +} +void SaveSystem::ReadBGMFlags(Io::MemoryStream& stream) { + Io::ReadArrayLE(BGMFlags.data(), &stream, 200); +} + +void SaveSystem::LoadScrWork() { + ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO]; + ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1]; + ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2]; + ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO]; + ScrWork[SW_SVBGM2NO] = ScrWork[SW_BGMREQNO2]; + ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2]; + ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3]; + ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4]; + ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5]; + for (int i = 0; i < 8; i++) { + ScrWork[SW_SVBGNO1 + i] = ScrWork[SW_BG1NO + i * ScrWorkBgStructSize]; + ScrWork[SW_SVCHANO1 + i] = ScrWork[SW_CHA1NO + i * ScrWorkChaStructSize]; + } +} +void SaveSystem::UpdateWorkingSaveEntry(CCLCC::SaveFileEntry* entry) { + *dynamic_cast(entry) = + *dynamic_cast(WorkingSaveEntry.get()); +} + +} // namespace CCLCC_PS4 +} // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/savesystem_ps4.h b/src/games/cclcc/savesystem_ps4.h new file mode 100644 index 000000000..392c64a08 --- /dev/null +++ b/src/games/cclcc/savesystem_ps4.h @@ -0,0 +1,110 @@ +#pragma once +#include "savesystem.h" + +#include "../../data/savesystem.h" +#include "../../texture/texture.h" +#include "../../io/memorystream.h" +#include "../../spritesheet.h" +#include + +namespace Impacto { +namespace CCLCC_PS4 { + +using namespace Impacto::SaveSystem; + +constexpr size_t SaveEntrySize = 0x1b110; +constexpr size_t SystemSaveSize = 0x387c; +constexpr int SaveFileSize = + SaveEntrySize * MaxSaveEntries * 2 + SystemSaveSize; + +constexpr int SaveThumbnailWidth = 240; +constexpr int SaveThumbnailHeight = 135; +// CCLCC PS4 Save thumbnails are 240x135 RGB16 +constexpr int SaveThumbnailSize = + SaveThumbnailWidth * SaveThumbnailHeight * 4 / 2; + +struct SaveFileEntry final : CCLCC::SaveFileEntry { + public: + std::array FlagWorkScript1; // 50 bytes from &FlagWork[50] + std::array FlagWorkScript2; // 100 bytes from &FlagWork[300] + std::array ScrWorkScript1; // 2400 bytes from &ScrWork[1000] + std::array ScrWorkScript2; // 12000 bytes from &ScrWork[4300] + std::array MapLoadData{}; + std::array YesNoData{}; + std::array WaveData{}; + std::array ThumbnailData{}; + + std::span GetFlagWorkScript1() const override { + return FlagWorkScript1; + } + std::span GetFlagWorkScript2() const override { + return FlagWorkScript2; + } + std::span GetScrWorkScript1() const override { + return ScrWorkScript1; + } + std::span GetScrWorkScript2() const override { + return ScrWorkScript2; + } + std::span GetMapLoadData() const override { + return MapLoadData; + } + std::span GetYesNoData() const override { return YesNoData; } + std::span GetWaveData() const override { return WaveData; } + std::span GetThumbnailData() const override { + return ThumbnailData; + } +}; + +class SaveSystem final : public CCLCC::SaveSystem { + private: + std::array GameExtraData{}; + std::array MessageFlags{}; + std::array SystemData{}; + std::array EVFlags{}; + std::array BGMFlags{}; + + std::span GetGameExtraData() override { return GameExtraData; } + std::span GetGameExtraData() const override { + return GameExtraData; + } + std::span GetMessageFlags() override { return MessageFlags; } + std::span GetMessageFlags() const override { + return MessageFlags; + } + std::span GetSystemData() override { return SystemData; } + std::span GetEVFlags() override { return EVFlags; } + std::span GetEVFlags() const override { return EVFlags; } + std::span GetBGMFlags() override { return BGMFlags; } + std::span GetBGMFlags() const override { return BGMFlags; } + + public: + SaveSystem() = default; + SaveError CheckSaveFile() const override; + SaveError MountSaveFile(std::vector& textures) override; + void LoadEntry(SaveType type, int id) override; + + const CCLCC::SaveLayout& GetSaveLayout() const override; + void InitSaveSlots() override; + + // Flags and Scr + void WriteWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void ReadWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void WriteWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void ReadWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + + void WriteBGMFlags(Io::MemoryStream& stream) override; + void ReadBGMFlags(Io::MemoryStream& stream) override; + + void LoadScrWork() override; + + SaveError WriteSaveFile() override; + void UpdateWorkingSaveEntry(CCLCC::SaveFileEntry* entry) override; +}; + +} // namespace CCLCC_PS4 +} // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/savesystem_switch.cpp b/src/games/cclcc/savesystem_switch.cpp new file mode 100644 index 000000000..edfdc6b94 --- /dev/null +++ b/src/games/cclcc/savesystem_switch.cpp @@ -0,0 +1,484 @@ +#include "savesystem_switch.h" + +#include "../../io/stream.h" +#include "../../io/physicalfilestream.h" +#include "../../mem.h" +#include "../../vm/vm.h" +#include "../../profile/data/savesystem.h" +#include "../../data/savesystem.h" +#include "../../profile/scriptvars.h" +#include "../../profile/vm.h" +#include "../../renderer/renderer.h" +#include "../../profile/configsystem.h" +#include "../../effects/wave.h" +#include "../../audio/audiosystem.h" + +#include "mapsystem.h" +#include "yesnotrigger.h" + +#include +#include +#include +#include +#include + +namespace Impacto { +namespace CCLCC_Switch { + +using namespace Impacto::Vm; +using namespace Impacto::Effects; +using namespace Impacto::SaveSystem; +using namespace Impacto::Profile::SaveSystem; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Profile::Vm; +using namespace Impacto::Profile::ConfigSystem; + +static constexpr CCLCC::SaveLayout SwitchLayout{ + .ConfigOffset = 0x58C, + .VoiceOffset = 0x59E, + .SkipVoiceOffset = 0x5E1, + .AdvanceTextOffset = 0x5E5, + .QuickSortedIdOffset = 0x90E, + .QuickSortedIdIsLoaded = false, + .EVFlagsOffset = 0x96E, + .BgmFlagsOffset = 0xA0E, + .MessageFlagsOffset = 0xA2E, + .ExtraDataOffset = 0x20A2E, + + .SysFlagWorkDst1 = 200, + .SysFlagWorkDst2 = 560, + .SysScrWorkDst1 = 1800, + .SysScrWorkLen1 = 200, + .FlagWork2Src = 400, + + .MainThreadOffset = 0x7CDC, + .MainThreadBufIdOffset = 0x7D40, + .WaveOffset = 0x7D88, + .MapLoadOffset = 0x82C8, + .ThumbnailPadding = 0xED0, + .ThumbnailWidth = 240, + .ThumbnailHeight = 135, +}; + +SaveError SaveSystem::CheckSaveFile() const { + auto checkFile = [](std::string path, int size) { + std::error_code ec; + + IoError existsState = Io::PathExists(path); + if (existsState == IoError_NotFound) { + return SaveError::NotFound; + } else if (existsState == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to check if save file exists, error: \"{:s}\"\n", + ec.message()); + return SaveError::Failed; + } + + auto saveFileSize = Io::GetFileSize(path); + if (saveFileSize == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to get save file size, error: \"{:s}\"\n", ec.message()); + return SaveError::Failed; + } else if (saveFileSize != size) { + return SaveError::Corrupted; + } + + Io::FilePermissionsFlags perms; + IoError permsState = Io::GetFilePermissions(path, perms); + using enum Io::FilePermissionsFlags; + if (permsState == IoError_Fail) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to get save file permissions, error: \"{:s}\"\n", + ec.message()); + return SaveError::Failed; + } else if ((perms & owner_read) == none || (perms & owner_write) == none) { + return SaveError::WrongUser; + } + + return SaveError::OK; + }; + + auto error = checkFile(SaveFilePath, SaveFileSize); + if (error != SaveError::OK) { + return error; + } + + error = checkFile(*SystemDataPath, SystemSaveSize); + if (error != SaveError::OK) { + return error; + } + + return SaveError::OK; +} + +static std::optional IoErrorToSaveError(IoError err) { + switch (err) { + case IoError_NotFound: + return SaveError::NotFound; + case IoError_Fail: + case IoError_Eof: + return SaveError::Corrupted; + case IoError_OK: + return std::nullopt; + } + return SaveError::Failed; +} + +SaveError SaveSystem::MountSaveFile(std::vector& textures) { + namespace fs = std::filesystem; + + Io::Stream* systemDataStream; + IoError err = + Io::PhysicalFileStream::Create(*SystemDataPath, &systemDataStream); + + if (auto saveErr = IoErrorToSaveError(err)) return *saveErr; + + const RectF viewport = Window->GetViewport(); + WorkingSaveEntry = std::make_unique(SaveFileEntry{}); + WorkingSaveThumbnail.Sheet = SpriteSheet(viewport.Width, viewport.Height); + WorkingSaveThumbnail.Bounds.SetSize(viewport.GetSize()); + + QueuedTexture txt{ + .Id = std::ref(WorkingSaveThumbnail.Sheet.Texture), + }; + txt.Tex.LoadSolidColor((int)WorkingSaveThumbnail.Bounds.Width, + (int)WorkingSaveThumbnail.Bounds.Height, 0x000000); + textures.push_back(txt); + + Io::ReadArrayLE(SystemData.data(), systemDataStream, + SystemData.size()); + delete systemDataStream; + + Io::Stream* saveDataStream; + err = Io::PhysicalFileStream::Create(SaveFilePath, &saveDataStream); + if (auto saveErr = IoErrorToSaveError(err)) return *saveErr; + + /* + uint32_t systemSaveChecksum = + CalculateChecksum(std::span(SystemData).subspan(4)); + */ + + textures.reserve(MaxSaveEntries * 2); + + auto readEntry = [&](Io::Stream* stream, SaveFileEntryBase** entries, int i, + SaveType saveType) { + std::array entrySlotBuf; + Io::ReadArrayLE(entrySlotBuf.data(), stream, entrySlotBuf.size()); + Io::MemoryStream saveEntryDataStream(entrySlotBuf.data(), + entrySlotBuf.size(), false); + + QueuedTexture tex{ + .Id = std::ref(entries[i]->SaveThumbnail.Sheet.Texture), + }; + LoadEntryBuffer(saveEntryDataStream, + static_cast(*entries[i]), saveType, + tex.Tex); + + textures.push_back(tex); + }; + + // init and read full saves + init empty quick saves + for (int i = 0; i < MaxSaveEntries; i++) { + FullSaveEntries[i] = new SaveFileEntry(); + QuickSaveEntries[i] = new SaveFileEntry(); + + readEntry(saveDataStream, FullSaveEntries, i, SaveType::Full); + + // Todo, validate checksum? + } + delete saveDataStream; + + // read quick saves + fs::path qsavePath = *QuickDataPath; + fs::path folder = qsavePath.parent_path(); + std::string stem = qsavePath.stem().string(); // MDATA_CC + std::string ext = qsavePath.extension().string(); // .DAT + + std::regex pattern(stem + R"((\d{3}))" + "\\" + ext, std::regex::icase); + std::vector> idsWithTime; + + int lockedQuickSaveSlots = 0; + for (const auto& entry : fs::directory_iterator(folder)) { + if (!entry.is_regular_file()) continue; + + std::string filename = entry.path().filename().string(); + std::smatch match; + + if (!std::regex_match(filename, match, pattern) || match.size() < 1) + continue; + + fs::file_time_type writeTime = fs::last_write_time(entry.path()); + + Io::Stream* qsaveStream; + err = Io::PhysicalFileStream::Create(entry.path().string(), &qsaveStream); + if (auto saveErr = IoErrorToSaveError(err)) return *saveErr; + + int id = std::stoi(match[1].str()); + if (id < 0 || id >= MaxSaveEntries) { + assert(false); + continue; + } + idsWithTime.push_back({static_cast(id), writeTime}); + + readEntry(qsaveStream, QuickSaveEntries, id, SaveType::Quick); + + lockedQuickSaveSlots += + static_cast(*QuickSaveEntries[id]).Flags & WriteProtect; + + delete qsaveStream; + } + + std::sort(idsWithTime.begin(), idsWithTime.end(), + [](const auto& a, const auto& b) { return a.second > b.second; }); + + // fill QuickSaveRecentSortedId based on the file modification time, since + // engine does not update System save file on quicksave file write + std::array used{}; + size_t i = 0; + for (const auto& [id, time] : idsWithTime) { + QuickSaveRecentSortedId[i++] = id; + used[id] = true; + } + + // fill the rest of the ids + if (idsWithTime.size() != MaxSaveEntries) { + for (uint8_t j = 0; j < MaxSaveEntries && i < MaxSaveEntries; ++j) { + if (!used[j]) QuickSaveRecentSortedId[i++] = j; + } + } + SetLockedQuickSaveCount(lockedQuickSaveSlots); + SetFlag(SF_SAVEALLPROTECTED, LockedQuickSaveCount == MaxSaveEntries); + + return SaveError::OK; +} + +static void ChecksumAndWriteEntry(Io::Stream* destStream, + std::span& entrySlotBuf, + Io::MemoryStream& saveEntryMemoryStream) { + uint32_t entryCheckSum = CCLCC::SaveSystem::CalculateChecksum( + std::span(entrySlotBuf).subspan(6, 64710 * 2), 18198, 5250, false); + saveEntryMemoryStream.Seek(2, SEEK_SET); + Io::WriteLE(&saveEntryMemoryStream, entryCheckSum >> 16); + Io::WriteLE(&saveEntryMemoryStream, entryCheckSum & 0xFFFF); + Io::WriteArrayLE(entrySlotBuf.data(), destStream, + entrySlotBuf.size()); +} + +SaveError SaveSystem::WriteSaveFile() { + using CF = Io::PhysicalFileStream::CreateFlagsMode; + Io::Stream* stream; + IoError err = Io::PhysicalFileStream::Create( + *SystemDataPath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE); + if (err != IoError_OK) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to open save file for writing\n"); + return SaveError::Failed; + } + + stream->Seek(0, SEEK_SET); + Io::MemoryStream systemSaveStream = + Io::MemoryStream(SystemData.data(), SystemData.size(), false); + uint32_t systemChecksum = CalculateChecksum(std::span(SystemData).subspan(4)); + systemSaveStream.Seek(0, SEEK_SET); + Io::WriteLE(&systemSaveStream, systemChecksum >> 16); + Io::WriteLE(&systemSaveStream, systemChecksum & 0xFFFF); + Io::WriteArrayLE(SystemData.data(), stream, SystemData.size()); + delete stream; + + err = Io::PhysicalFileStream::Create( + SaveFilePath, &stream, CF::CREATE | CF::CREATE_DIRS | CF::WRITE); + if (err != IoError_OK) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to open save file for writing\n"); + return SaveError::Failed; + } + + stream->Seek(0, SEEK_SET); + + for (int i = 0; i < MaxSaveEntries; i++) { + SaveFileEntry* entry = (SaveFileEntry*)FullSaveEntries[i]; + if (entry == nullptr || entry->Status == 0) { + Io::WriteLE(stream, 0, SaveEntrySize); + } else { + assert(stream->Position == static_cast(SaveEntrySize) * i); + std::vector entrySlotBuf = std::vector(SaveEntrySize); + Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(), + entrySlotBuf.size(), false); + SaveEntryBuffer(saveEntryMemoryStream, *entry, SaveType::Full); + auto spanned = std::span(entrySlotBuf); + ChecksumAndWriteEntry(stream, spanned, saveEntryMemoryStream); + } + } + return SaveError::OK; +} + +SaveError SaveSystem::WriteQuickSaveFile() { + int id = QuickSaveRecentSortedId[0]; + auto* entry = GetSaveEntry(SaveType::Quick, 0); + + namespace fs = std::filesystem; + fs::path qsavePath = *QuickDataPath; + std::string stem = qsavePath.stem().string(); // MDATA_CC + std::string ext = qsavePath.extension().string(); // .DAT + std::string filename = fmt::format("{}{:03d}{}", stem, id, ext); + std::string targetPath = qsavePath.replace_filename(filename).string(); + + using CF = Io::PhysicalFileStream::CreateFlagsMode; + Io::Stream* stream; + IoError err = Io::PhysicalFileStream::Create( + targetPath, &stream, + CF::CREATE | CF::CREATE_DIRS | CF::WRITE | CF::TRUNCATE); + if (err != IoError_OK) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to open save file for writing\n"); + return SaveError::Failed; + } + + stream->Seek(0, SEEK_SET); + + std::vector entrySlotBuf = std::vector(SaveEntrySize); + Io::MemoryStream saveEntryMemoryStream(entrySlotBuf.data(), + entrySlotBuf.size(), false); + SaveEntryBuffer(saveEntryMemoryStream, *entry, SaveType::Quick); + auto spanned = std::span(entrySlotBuf); + ChecksumAndWriteEntry(stream, spanned, saveEntryMemoryStream); + return SaveError::OK; +} + +const CCLCC::SaveLayout& SaveSystem::GetSaveLayout() const { + return SwitchLayout; +} + +void SaveSystem::LoadEntry(SaveType type, int id) { + if (!WorkingSaveEntry) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Failed to load save memory: no working save\n"); + return; + } + WorkingSaveEntry = + std::make_unique(*GetSaveEntry(type, id)); +} + +void SaveSystem::InitSaveSlots() { + std::for_each_n(QuickSaveEntries, MaxSaveEntries, + [](auto& ptr) { ptr = new SaveFileEntry(); }); + std::for_each_n(FullSaveEntries, MaxSaveEntries, + [](auto& ptr) { ptr = new SaveFileEntry(); }); + WorkingSaveEntry = std::make_unique(SaveFileEntry{}); +} + +void SaveSystem::ReadWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::ReadArrayLE(localEntry->FlagWorkScript2.data(), &stream, + localEntry->FlagWorkScript2.size()); + Io::ReadArrayLE(localEntry->FlagWorkScript1.data(), &stream, + localEntry->FlagWorkScript1.size()); + Io::ReadLE(&stream); + assert(stream.Position == 380); + Io::ReadArrayLE(localEntry->ScrWorkScript2.data(), &stream, + localEntry->ScrWorkScript2.size()); + assert(stream.Position == 12380); + Io::ReadArrayLE(localEntry->ScrWorkScript1.data(), &stream, + localEntry->ScrWorkScript1.size()); + // StrWork goes here, skipping + stream.Seek(0x4000, SEEK_CUR); +} + +void SaveSystem::WriteWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + Io::WriteArrayLE(localEntry->FlagWorkScript2.data(), &stream, + localEntry->FlagWorkScript2.size()); + Io::WriteArrayLE(localEntry->FlagWorkScript1.data(), &stream, + localEntry->FlagWorkScript1.size()); + Io::WriteLE(&stream, 0); + assert(stream.Position == 380); + Io::WriteArrayLE(localEntry->ScrWorkScript2.data(), &stream, + localEntry->ScrWorkScript2.size()); + assert(stream.Position == 12380); + Io::WriteArrayLE(localEntry->ScrWorkScript1.data(), &stream, + localEntry->ScrWorkScript1.size()); + // StrWork goes here, skipping + stream.Seek(0x4000, SEEK_CUR); +} + +void SaveSystem::WriteBGMFlags(Io::MemoryStream& stream) { + for (size_t i = 0; i < 32; i++) { + const uint8_t bgmByte = (static_cast(BGMFlags[8 * i])) | + (static_cast(BGMFlags[8 * i + 1]) << 1) | + (static_cast(BGMFlags[8 * i + 2]) << 2) | + (static_cast(BGMFlags[8 * i + 3]) << 3) | + (static_cast(BGMFlags[8 * i + 4]) << 4) | + (static_cast(BGMFlags[8 * i + 5]) << 5) | + (static_cast(BGMFlags[8 * i + 6]) << 6) | + (static_cast(BGMFlags[8 * i + 7]) << 7); + Io::WriteLE(&stream, bgmByte); + } +} +void SaveSystem::ReadBGMFlags(Io::MemoryStream& stream) { + for (int i = 0; i < 32; i++) { + auto val = Io::ReadU8(&stream); + BGMFlags[8 * i] = val & 1; + BGMFlags[8 * i + 1] = (val & 2) != 0; + BGMFlags[8 * i + 2] = (val & 4) != 0; + BGMFlags[8 * i + 3] = (val & 8) != 0; + BGMFlags[8 * i + 4] = (val & 0x10) != 0; + BGMFlags[8 * i + 5] = (val & 0x20) != 0; + BGMFlags[8 * i + 6] = (val & 0x40) != 0; + BGMFlags[8 * i + 7] = val >> 7; + } +} + +void SaveSystem::WriteWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + + Io::WriteArrayLE(localEntry->WaveData.data(), &stream, + localEntry->WaveData.size()); + stream.Seek(132, SEEK_CUR); // skip ripple data (part of the wave data, but + // not used in CCLCC) +}; + +void SaveSystem::ReadWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) { + SaveFileEntry* localEntry = static_cast(&entry); + + Io::ReadArrayLE(localEntry->WaveData.data(), &stream, + localEntry->WaveData.size()); + stream.Seek(132, SEEK_CUR); // skip ripple data +}; + +void SaveSystem::LoadScrWork() { + ScrWork[SW_SVSENO] = ScrWork[SW_SEREQNO]; + ScrWork[SW_SVSENO + 1] = ScrWork[SW_SEREQNO + 1]; + ScrWork[SW_SVSENO + 2] = ScrWork[SW_SEREQNO + 2]; + ScrWork[SW_SVBGMNO] = ScrWork[SW_BGMREQNO]; + ScrWork[SW_SVBGM2NO] = ScrWork[SW_BGMREQNO2]; + ScrWork[SW_SVSCRNO1] = ScrWork[SW_SCRIPTNO2]; + ScrWork[SW_SVSCRNO2] = ScrWork[SW_SCRIPTNO3]; + ScrWork[SW_SVSCRNO3] = ScrWork[SW_SCRIPTNO4]; + ScrWork[SW_SVSCRNO4] = ScrWork[SW_SCRIPTNO5]; + for (int i = 0; i < 8; i++) { + ScrWork[SW_SVBGNO1 + i] = ScrWork[SW_BG1NO + i * ScrWorkBgStructSize]; + } + for (int i = 0; i < 10; i++) { + const auto charOffset = i * ScrWorkChaStructSize; + + ScrWork[SW_SVCHANO1 + i] = ScrWork[SW_CHA1NO + charOffset] + + (ScrWork[SW_CHA1FACE + charOffset] << 16); + } + + for (int i = 0; i < 8; i++) { + ScrWork[SW_PIC_ARCHIVENO1 + i] = ScrWork[SW_PIC_REQ_ARCHIVENO1 + 2 * i]; + ScrWork[SW_PIC_FILENO1 + i] = ScrWork[SW_PIC_REQ_FILENO1 + 2 * i]; + } +} + +void SaveSystem::UpdateWorkingSaveEntry(CCLCC::SaveFileEntry* entry) { + *dynamic_cast(entry) = + *dynamic_cast(WorkingSaveEntry.get()); +} + +} // namespace CCLCC_Switch +} // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/savesystem_switch.h b/src/games/cclcc/savesystem_switch.h new file mode 100644 index 000000000..e3b45847c --- /dev/null +++ b/src/games/cclcc/savesystem_switch.h @@ -0,0 +1,113 @@ +#pragma once + +#include "savesystem.h" +#include "../../data/savesystem.h" +#include "../../texture/texture.h" +#include "../../io/memorystream.h" +#include "../../spritesheet.h" +#include + +namespace Impacto { +namespace CCLCC_Switch { + +using namespace Impacto::SaveSystem; + +constexpr size_t SaveEntrySize = 0x1F9E8; +constexpr size_t SystemSaveSize = 0x21020; +constexpr int SaveFileSize = SaveEntrySize * MaxSaveEntries; + +constexpr int SaveThumbnailWidth = 240; +constexpr int SaveThumbnailHeight = 135; +// CCLCC Switch Save thumbnails are 240x135 RGB16 +constexpr int SaveThumbnailSize = + SaveThumbnailWidth * SaveThumbnailHeight * 4 / 2; + +struct SaveFileEntry final : CCLCC::SaveFileEntry { + public: + std::array FlagWorkScript1{}; // 150 bytes from &FlagWork[50] + std::array FlagWorkScript2{}; // 100 bytes from &FlagWork[400] + std::array ScrWorkScript1{}; // 3200 bytes from &ScrWork[1000] + std::array ScrWorkScript2{}; // 12000 bytes from &ScrWork[4300] + std::array MapLoadData{}; + std::array YesNoData{}; + std::array WaveData{}; + std::array ThumbnailData{}; + + std::span GetFlagWorkScript1() const override { + return FlagWorkScript1; + } + std::span GetFlagWorkScript2() const override { + return FlagWorkScript2; + } + std::span GetScrWorkScript1() const override { + return ScrWorkScript1; + } + std::span GetScrWorkScript2() const override { + return ScrWorkScript2; + } + std::span GetMapLoadData() const override { + return MapLoadData; + } + std::span GetYesNoData() const override { return YesNoData; } + std::span GetWaveData() const override { return WaveData; } + std::span GetThumbnailData() const override { + return ThumbnailData; + } +}; + +class SaveSystem final : public CCLCC::SaveSystem { + private: + std::array GameExtraData{}; + std::array MessageFlags{}; + std::array SystemData{}; + std::array EVFlags{}; + std::array BGMFlags{}; + + std::span GetGameExtraData() override { return GameExtraData; } + std::span GetGameExtraData() const override { + return GameExtraData; + } + std::span GetMessageFlags() override { return MessageFlags; } + std::span GetMessageFlags() const override { + return MessageFlags; + } + std::span GetSystemData() override { return SystemData; } + std::span GetEVFlags() override { return EVFlags; } + std::span GetEVFlags() const override { return EVFlags; } + std::span GetBGMFlags() override { return BGMFlags; } + std::span GetBGMFlags() const override { return BGMFlags; } + + public: + SaveSystem() = default; + + SaveError CheckSaveFile() const override; + SaveError MountSaveFile(std::vector& textures) override; + void LoadEntry(SaveType type, int id) override; + + const CCLCC::SaveLayout& GetSaveLayout() const override; + void InitSaveSlots() override; + + SaveError WriteQuickSaveFile() override; + + // Flags and Scr + void WriteWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void ReadWorkScriptData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void WriteWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + void ReadWaveData(Io::MemoryStream& stream, + CCLCC::SaveFileEntry& entry) override; + + void WriteBGMFlags(Io::MemoryStream& stream) override; + void ReadBGMFlags(Io::MemoryStream& stream) override; + + void LoadScrWork() override; + + SaveError WriteSaveFile() override; + + void UpdateWorkingSaveEntry(CCLCC::SaveFileEntry* entry) override; +}; + +} // namespace CCLCC_Switch +} // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/systemmenu.cpp b/src/games/cclcc/systemmenu.cpp index bf3636977..67f43c6eb 100644 --- a/src/games/cclcc/systemmenu.cpp +++ b/src/games/cclcc/systemmenu.cpp @@ -274,7 +274,7 @@ void SystemMenu::Init() { MainItems->Children[static_cast(MenuItems::Backlog)]) ->IsLocked = backlogLockState; - // these flag need to be recalculated after loading a game + // this flag needs to be recalculated after loading a game bool noFreeSlots = SaveSystem::MaxSaveEntries == SaveSystem::Implementation->GetLockedQuickSaveCount(); SetFlag(SF_SAVEALLPROTECTED, noFreeSlots); diff --git a/src/games/cclcc/tipsmenu.cpp b/src/games/cclcc/tipsmenu.cpp index cf2066405..c5f540f55 100644 --- a/src/games/cclcc/tipsmenu.cpp +++ b/src/games/cclcc/tipsmenu.cpp @@ -5,6 +5,7 @@ #include "../../vm/vm.h" #include "../../inputsystem.h" #include "../../profile/scriptvars.h" +#include "../../profile/vm.h" #include "../../profile/dialogue.h" #include "../../profile/ui/backlogmenu.h" #include "../../profile/ui/tipsmenu.h" @@ -100,7 +101,10 @@ void TipsMenu::Hide() { if (State != Hidden) { State = Hiding; FadeAnimation.StartOut(); - Audio::PlayInGroup(Audio::ACG_SE, "sysse", 3, false, 0); + // Switch has this sfx played in the script + if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::LCCSwitch) { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 3, false, 0); + } if (ScrWork[SW_SYSSUBMENUCT] != 0) { TransitionAnimation.StartOut(); } else { @@ -289,6 +293,7 @@ void TipsMenu::Render() { void TipsMenu::Init() { auto* TipRecords = TipsSystem::GetTipRecords(); + SortedTipIds.clear(); std::transform( TipRecords->begin(), TipRecords->end(), std::back_inserter(SortedTipIds), [](TipsSystem::TipsDataRecord const& record) { return record.Id; }); @@ -307,9 +312,8 @@ void TipsMenu::Init() { void TipsMenu::SwitchToTipId(int id) { if (id - 1 == CurrentlyDisplayedTipId) return; int actualId = SortedTipIds[id - 1]; + auto [buffers, tipsScrBufId] = TipsSystem::GetTipsScriptBufferCtx(); auto* record = TipsSystem::GetTipRecord(actualId); - uint32_t tipsScrBufId = TipsSystem::GetTipsScriptBufferId(); - if (record->IsLocked) { Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); return; @@ -317,32 +321,46 @@ void TipsMenu::SwitchToTipId(int id) { CurrentlyDisplayedTipId = id - 1; TipsSystem::SetTipUnreadState(actualId, false); - Category->SetText(Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, - .IpOffset = record->StringAdr[0]}, - CategoryPos, (float)CategoryFontSize, + auto categoryStr = TipsSystem::GetTextStringStream(actualId, 0); + auto nameStr = TipsSystem::GetTextStringStream(actualId, 1); + auto pronunciationStr = TipsSystem::GetTextStringStream(actualId, 2); + Category->SetText(categoryStr, CategoryPos, (float)CategoryFontSize, RendererOutlineMode::None, {TipsMenuDarkTextColor, 0}); - Name->SetText(Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, - .IpOffset = record->StringAdr[1]}, - NamePos, (float)NameFontSize, RendererOutlineMode::None, - {TipsMenuDarkTextColor, 0}); - Pronunciation->SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, - .IpOffset = record->StringAdr[2]}, - PronunciationPos, (float)PronunciationFontSize, RendererOutlineMode::None, - 0); + Name->SetText(nameStr, NamePos, (float)NameFontSize, + RendererOutlineMode::None, {TipsMenuDarkTextColor, 0}); + Pronunciation->SetText(pronunciationStr, PronunciationPos, + (float)PronunciationFontSize, + RendererOutlineMode::None, 0); { - uint16_t sc3StringBuffer[4]; - TextGetSc3String(fmt::format("{:03d}", id), sc3StringBuffer); - Vm::Sc3Stream stream(sc3StringBuffer); - stream = Vm::Sc3Stream(sc3StringBuffer); + using Buffer = + std::variant, std::array>; + + Buffer sc3StringBuffer = + Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32 + ? Buffer(std::in_place_type>) + : Buffer(std::in_place_type>); + + auto lambda = [&]() { + auto& buf = std::get>(sc3StringBuffer); + TextGetSc3String(fmt::format("{:03d}", id), buf); + return Vm::Sc3Stream(buf.data()); + }; + + auto stream = Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32 + ? lambda.template operator()() + : lambda.template operator()(); + Number->SetText(stream, NumberPos, (float)NumberFontSize, RendererOutlineMode::None, 0); } Vm::Sc3VmThread dummy; - dummy.IpOffset = record->StringAdr[4]; dummy.ScriptBufferId = tipsScrBufId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + dummy.SetStringIp(TipsSystem::GetTextStringStream(actualId, 4).Data()); TextPage.Clear(); TextPage.AddString(&dummy); TipViewItems.HasFocus = true; diff --git a/src/games/cclcc/tipssystem.cpp b/src/games/cclcc/tipssystem.cpp index bf47f6517..d14e23968 100644 --- a/src/games/cclcc/tipssystem.cpp +++ b/src/games/cclcc/tipssystem.cpp @@ -4,6 +4,7 @@ #include "../../vm/vm.h" #include "../../io/memorystream.h" #include "../../profile/data/tipssystem.h" +#include "../../profile/vm.h" #include "../../ui/ui.h" namespace Impacto { @@ -34,8 +35,11 @@ void TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr, record.Id = (uint16_t)TipEntryCount; record.NumberOfContentStrings = (uint16_t)numberOfContentStrings; for (int i = 0; i < numberOfContentStrings + 4; i++) { - record.StringAdr[i] = - ScriptGetStrAddress(scriptBufferId, ReadLE(&stream)); + auto lineId = Profile::Vm::StringIdSize == 4 ? ReadLE(&stream) + : ReadLE(&stream); + record.StringAdr[i] = Profile::Vm::UseMsbStrings + ? MsbGetStrAddress(scriptBufferId, lineId) + : ScriptGetStrAddress(scriptBufferId, lineId); } Records[TipEntryCount] = std::move(record); diff --git a/src/games/cclcc/titlemenu.cpp b/src/games/cclcc/titlemenu.cpp index 3b32ca860..73483d643 100644 --- a/src/games/cclcc/titlemenu.cpp +++ b/src/games/cclcc/titlemenu.cpp @@ -13,7 +13,9 @@ #include "../../profile/scriptvars.h" #include "../../profile/game.h" #include "../../profile/patch.h" +#include "../../profile/vm.h" #include "../../vm/interface/input.h" +#include "../../vm/vm.h" #include "../../audio/audiosystem.h" #include "../../profile/scriptinput.h" @@ -31,6 +33,72 @@ using namespace Impacto::Vm::Interface; using namespace Impacto::UI::Widgets::CCLCC; +// game version agnostic +enum TitleMenuState : uint8_t { + Invisible, + PressToStart, + StartTransition, + Main, + FadingOut, + SubMenu, + InitialFade, + ClearList, + FadingIn +}; + +static TitleMenuState LookupTitleMenuState(int scriptState) { + ImpLogSlow(LogLevel::Trace, LogChannel::General, "title menu state: {}\n", + scriptState); + if (Profile::Vm::GameInstructionSet == + Impacto::Vm::InstructionSet::LCCSwitch) { + using namespace UI::CC::TitleMenuMode::Switch; + switch (scriptState) { + case Mode::Invisible: + return Invisible; + case Mode::PressToStart: + return PressToStart; + case Mode::StartTransition: + return StartTransition; + case 9: + case Mode::Main: + return Main; + case Mode::FadingOut: + return FadingOut; + case Mode::SubMenu: + return SubMenu; + case Mode::InitialFade: + return InitialFade; + case Mode::ClearList: + return ClearList; + case Mode::FadingIn: + return FadingIn; + } + } else { + switch (scriptState) { + using namespace UI::CC::TitleMenuMode::PS4; + case Mode::Invisible: + return Invisible; + case Mode::PressToStart: + return PressToStart; + case Mode::StartTransition: + return StartTransition; + case Mode::Main: + return Main; + case Mode::FadingOut: + return FadingOut; + case Mode::SubMenu: + return SubMenu; + case Mode::InitialFade: + return InitialFade; + case Mode::ClearList: + return ClearList; + } + } + ImpLog(LogLevel::Error, LogChannel::General, "Unknown title menu state: {}\n", + scriptState); + return InitialFade; +} + void TitleMenu::MenuButtonOnClick(Widgets::Button* target) { TitleButton* button = static_cast(target); Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); @@ -125,8 +193,8 @@ TitleMenu::TitleMenu() { }; // NewGame menu button - NewGame = new TitleButton(0, MenuEntriesSprites[0], MenuEntriesHSprites[0], - ItemHighlightSprite, + NewGame = new TitleButton(NewButtonId, MenuEntriesSprites[0], + MenuEntriesHSprites[0], ItemHighlightSprite, glm::vec2((-1.0f) + ItemHighlightOffset.x, (ItemYBase + (0 * ItemPadding)))); setupBtn(NewGame, onClick, MainItems, FDIR_DOWN); @@ -151,13 +219,15 @@ TitleMenu::TitleMenu() { // Config menu button Config = new TitleButton( - 30, MenuEntriesSprites[3], MenuEntriesHSprites[3], ItemHighlightSprite, + ConfigButtonId, MenuEntriesSprites[3], MenuEntriesHSprites[3], + ItemHighlightSprite, glm::vec2(ItemHighlightOffset.x, (ItemYBase + (3 * ItemPadding)))); setupBtn(Config, onClick, MainItems, FDIR_DOWN); // Help menu button Help = new TitleButton( - 40, MenuEntriesSprites[4], MenuEntriesHSprites[4], ItemHighlightSprite, + HelpButtonId, MenuEntriesSprites[4], MenuEntriesHSprites[4], + ItemHighlightSprite, glm::vec2(ItemHighlightOffset.x, (ItemYBase + (4 * ItemPadding)))); setupBtn(Help, onClick, MainItems, FDIR_DOWN); @@ -174,34 +244,36 @@ TitleMenu::TitleMenu() { } // Load secondary Continue menu button - Load = new TitleButton(10, LoadSprite, LoadHighlightSprite, nullSprite, - glm::vec2(SecondaryFirstItemHighlightOffsetX, - (ItemYBase + (2 * ItemPadding)))); + Load = + new TitleButton(LoadButtonId, LoadSprite, LoadHighlightSprite, nullSprite, + glm::vec2(SecondaryFirstItemHighlightOffsetX, + (ItemYBase + (2 * ItemPadding)))); setupBtn(Load, onClick, ContinueItems, FDIR_RIGHT); // QuickLoad secondary Continue menu button - QuickLoad = - new TitleButton(11, QuickLoadSprite, QuickLoadHighlightSprite, nullSprite, - glm::vec2(SecondarySecondItemHighlightOffsetX, - (ItemYBase + (2 * ItemPadding)))); + QuickLoad = new TitleButton(QLoadButtonId, QuickLoadSprite, + QuickLoadHighlightSprite, nullSprite, + glm::vec2(SecondarySecondItemHighlightOffsetX, + (ItemYBase + (2 * ItemPadding)))); setupBtn(QuickLoad, onClick, ContinueItems, FDIR_RIGHT); // Tips secondary Extra menu button - Tips = new TitleButton(20, TipsSprite, TipsHighlightSprite, nullSprite, - glm::vec2(SecondaryFirstItemHighlightOffsetX, - (ItemYBase + (3 * ItemPadding)))); + Tips = + new TitleButton(TipsButtonId, TipsSprite, TipsHighlightSprite, nullSprite, + glm::vec2(SecondaryFirstItemHighlightOffsetX, + (ItemYBase + (3 * ItemPadding)))); setupBtn(Tips, onClick, ExtraItems, FDIR_RIGHT); // Library secondary Extra menu button - Library = - new TitleButton(21, LibrarySprite, LibraryHighlightSprite, nullSprite, - glm::vec2(SecondarySecondItemHighlightOffsetX, - (ItemYBase + (3 * ItemPadding)))); + Library = new TitleButton(LibraryButtonId, LibrarySprite, + LibraryHighlightSprite, nullSprite, + glm::vec2(SecondarySecondItemHighlightOffsetX, + (ItemYBase + (3 * ItemPadding)))); setupBtn(Library, onClick, ExtraItems, FDIR_RIGHT); // EndingList secondary Extra menu button - EndingList = new TitleButton(22, EndingListSprite, EndingListHighlightSprite, - nullSprite, + EndingList = new TitleButton(EndingListButtonId, EndingListSprite, + EndingListHighlightSprite, nullSprite, glm::vec2(SecondaryThirdItemHighlightOffsetX, (ItemYBase + (3 * ItemPadding)))); setupBtn(EndingList, onClick, ExtraItems, FDIR_RIGHT); @@ -286,9 +358,10 @@ void TitleMenu::Hide() { } void TitleMenu::UpdateInput(float dt) { - if (ScrWork[SW_TITLEMODE] == CC::TitleMenuMode::SubMenu || - ScrWork[SW_TITLEMODE] == CC::TitleMenuMode::ClearList || - ScrWork[SW_TITLEMODE] == CC::TitleMenuMode::Main) { + auto currentState = LookupTitleMenuState(ScrWork[SW_TITLEMODE]); + + if (currentState == SubMenu || currentState == ClearList || + currentState == Main) { if (!InputLocked && !PrevInputLocked) { if (SlideItemsAnimation.State == AnimationState::Playing || SecondaryFadeAnimation.State == AnimationState::Playing || @@ -388,9 +461,9 @@ void TitleMenu::Update(float dt) { MenuLabel->Update(dt); ContinueItems->Update(dt); ExtraItems->Update(dt); + auto mode = LookupTitleMenuState(ScrWork[SW_TITLEMODE]); - PressToStartTransitionCaptureSet &= - ScrWork[SW_TITLEMODE] == CC::TitleMenuMode::StartTransition; + PressToStartTransitionCaptureSet &= mode == StartTransition; if (GetFlag(SF_TITLEMODE)) { Show(); @@ -399,8 +472,7 @@ void TitleMenu::Update(float dt) { } if (State != Hidden && GetFlag(SF_TITLEMODE)) { - switch (ScrWork[SW_TITLEMODE]) { - using enum CC::TitleMenuMode::Mode; + switch (mode) { case PressToStart: { if (PressToStartAnimation.LoopMode != AnimationLoopMode::ReverseDirection) { @@ -421,6 +493,10 @@ void TitleMenu::Update(float dt) { case ClearList: { SubMenuUpdate(); } break; + default: { + ImpLog(LogLevel::Error, LogChannel::General, + "Unexpected title menu state: {}\n", mode); + } } if (SubMenuState == Hiding && ScrWork[SW_SYSSUBMENUCT] == 0) { SubMenuState = Hidden; @@ -428,7 +504,7 @@ void TitleMenu::Update(float dt) { SubMenuState = Shown; IsFocused = true; } - IsExploding &= ScrWork[SW_TITLEMODE] == CC::TitleMenuMode::StartTransition; + IsExploding &= mode == StartTransition; } } @@ -454,12 +530,12 @@ void TitleMenu::ExplodeScreenUpdate() { } if (TitleAnimation.IsOut() && !IsExploding) { - TitleAnimation.StartIn(); + TitleAnimation.StartIn(true); IsExploding = true; EverExploded = true; } if (TitleAnimation.IsIn() && !IsExploding) { - TitleAnimation.StartOut(); + TitleAnimation.StartOut(false); IsExploding = true; } TitleAnimationSprite.Show = true; @@ -632,9 +708,8 @@ void TitleMenu::Render() { : nullptr; const bool renderOverlay = currentActiveMenu == nullptr || currentActiveMenu->State == Hidden; - - switch (ScrWork[SW_TITLEMODE]) { - using enum CC::TitleMenuMode::Mode; + auto mode = LookupTitleMenuState(ScrWork[SW_TITLEMODE]); + switch (mode) { case PressToStart: { Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f)); @@ -691,6 +766,7 @@ void TitleMenu::Render() { } } break; + case FadingIn: case FadingOut: { Renderer->DrawSprite(MainBackgroundSprite, {0.0f, 0.0f}); @@ -706,10 +782,14 @@ void TitleMenu::Render() { CommonMenu::DrawSmoke(SmokeOpacityNormal * (1.0f - ScrWork[SW_TITLEDISPCT] / 32.0f)); } + // FadingIn is used only on switch and it increments CT instead on + // decrementing in that FSM state + const auto ct = (mode == FadingOut) ? ScrWork[SW_TITLEDISPCT] + : 32 - ScrWork[SW_TITLEDISPCT]; Renderer->DrawQuad(RectF(0.0f, 0.0f, Profile::Game::DesignWidth, Profile::Game::DesignHeight), - {0.0f, 0.0f, 0.0f, ScrWork[SW_TITLEDISPCT] / 32.0f}); + {0.0f, 0.0f, 0.0f, ct / 32.0f}); } break; case SubMenu: @@ -741,6 +821,10 @@ void TitleMenu::Render() { Renderer->DrawSprite(CopyrightTextSprite, CopyrightTextPos, {1.0f, 1.0f, 1.0f, progress}); } break; + default: { + ImpLog(LogLevel::Error, LogChannel::General, + "Unexpected title menu state: {}\n", mode); + } } int maskAlpha = ScrWork[SW_TITLEMASKALPHA]; diff --git a/src/games/cclcc/yesnotrigger.cpp b/src/games/cclcc/yesnotrigger.cpp index 74008a19f..46b543335 100644 --- a/src/games/cclcc/yesnotrigger.cpp +++ b/src/games/cclcc/yesnotrigger.cpp @@ -1,6 +1,7 @@ #include "yesnotrigger.h" #include "commonmenu.h" #include "../../profile/games/cclcc/yesnotrigger.h" +#include "../../profile/vm.h" #include "../../vm/interface/input.h" #include "../../audio/audiosystem.h" #include "../../renderer/renderer.h" @@ -34,11 +35,20 @@ int YesNoTrigger::Load(uint8_t* data) { BgSpritePos = glm::vec2(bgSpritePosX, bgSpritePosY); memcpy(&BgSpriteScale, data + dataSize, sizeof(float)); dataSize += 16; + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + // TODO: controls prompt alpha CT + // memcpy(&AlphaCT, data + dataSize, sizeof(int)) + dataSize += 4; + } memcpy(&BgTransition, data + dataSize, sizeof(float)); dataSize += 4; memcpy(&State, data + dataSize, sizeof(YesNoState)); dataSize += 4; - // PS4 has 64-bit pointers, vita has 32-bit pointers... + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + // padding + dataSize += 4; + } + // Switch/PS4 has 64-bit pointers, vita has 32-bit pointers... dataSize += sizeof(void*); memcpy(&CurArrIndex, data + dataSize, sizeof(int)); dataSize += 4; @@ -46,7 +56,12 @@ int YesNoTrigger::Load(uint8_t* data) { dataSize += 4; dataSize += sizeof(void*); // buffer pointer in their struct dataSize += 8; - assert(dataSize == 0x60); + + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + assert(dataSize == 0x68); + } else { + assert(dataSize == 0x60); + } if (State != YesNoState::None && State != YesNoState::Complete) { switch (BgType) { case BGType::BG0: @@ -93,10 +108,19 @@ int YesNoTrigger::Save(uint8_t* data) { dataSize += 4; memcpy(data + dataSize, &BgSpriteScale, sizeof(float)); dataSize += 16; + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + // controls prompt alpha CT + // memcpy(data + dataSize, &BgSpriteScale, sizeof(int)) + dataSize += 4; + } memcpy(data + dataSize, &BgTransition, sizeof(float)); dataSize += 4; memcpy(data + dataSize, &State, sizeof(YesNoState)); dataSize += 4; + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + // padding + dataSize += 4; + } dataSize += sizeof(void*); memcpy(data + dataSize, &CurArrIndex, sizeof(int)); dataSize += 4; @@ -105,7 +129,11 @@ int YesNoTrigger::Save(uint8_t* data) { dataSize += sizeof(void*); // buffer pointer in their struct dataSize += 8; - assert(dataSize == 0x60); + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { + assert(dataSize == 0x68); + } else { + assert(dataSize == 0x60); + } return dataSize; } @@ -124,7 +152,7 @@ void YesNoTrigger::UpdateYesNoPos(float startX, float startY, float startScale, void YesNoTrigger::Update(float dt) { if (ScrWork[SW_SYSSUBMENUCT] != 0 || ScrWork[SW_SYSMENUCT] != 0 || - ScrWork[6433] == 0 || !HasStarted) { + ScrWork[SW_YESNO_CT] == 0 || !HasStarted) { return; } @@ -213,11 +241,11 @@ void YesNoTrigger::Update(float dt) { break; } BgSpriteScale = std::clamp(BgSpriteScale, 1.0f, 3.0f); - ScrWork[6434] = TargetArrIndex; + ScrWork[SW_YESNO_TARGET_INDEX] = TargetArrIndex; } void YesNoTrigger::Render() { - if (ScrWork[6433] == 0 || Display == false) { + if (ScrWork[SW_YESNO_CT] == 0 || Display == false) { return; } @@ -228,7 +256,7 @@ void YesNoTrigger::Render() { const float bgYOffset = (bgSize.y - bgHeight) / 2.0f - 46.0f * (BgSpriteScale - 1.0f) * 0.5f; const float alpha = - std::clamp(static_cast(ScrWork[6433] << 3), 0.0f, 255.0f); + std::clamp(static_cast(ScrWork[SW_YESNO_CT] << 3), 0.0f, 255.0f); const glm::vec4 bgtint = glm::vec4(1.0f, 1.0f, 1.0f, alpha / 255.0f); ActiveBackground.Bounds = Rect(static_cast(BgSpritePos.x + bgXOffset), @@ -449,7 +477,7 @@ void YesNoTrigger::ChooseSelected() { DispSel = false; AllowInput = false; Selection = YesNoSelect::NONE; - ScrWork[6432] = to_underlying(Selection); + ScrWork[SW_YESNO_SELECTED] = to_underlying(Selection); Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0.0f); } diff --git a/src/games/chlcc/sysmesbox.cpp b/src/games/chlcc/sysmesbox.cpp index f00d39097..a24486784 100644 --- a/src/games/chlcc/sysmesbox.cpp +++ b/src/games/chlcc/sysmesbox.cpp @@ -194,7 +194,7 @@ void SysMesBox::Init() { void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Messages[MessageCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, @@ -206,7 +206,7 @@ void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { void SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Choices[ChoiceCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, diff --git a/src/games/chlcc/tipsmenu.cpp b/src/games/chlcc/tipsmenu.cpp index 61fc21f43..309d8d48a 100644 --- a/src/games/chlcc/tipsmenu.cpp +++ b/src/games/chlcc/tipsmenu.cpp @@ -4,6 +4,7 @@ #include "../../mem.h" #include "../../profile/scriptvars.h" #include "../../profile/dialogue.h" +#include "../../profile/vm.h" #include "../../profile/ui/tipsmenu.h" #include "../../profile/games/chlcc/tipsmenu.h" #include "../../profile/games/chlcc/commonmenu.h" @@ -290,9 +291,9 @@ void TipsMenu::Init() { int currentCategoryId = -1; // String of characters by which tips are sorted, taken from _system script - auto [scriptBufId, catStrAddr] = + auto [buffers, bufferId, catStrAddr] = Vm::ScriptGetTextTableStrAddress(TipsStringTable, CategoryStringIndex); - uint8_t* categoryString = &Vm::ScriptBuffers[scriptBufId][catStrAddr]; + uint8_t* categoryString = &buffers[bufferId][catStrAddr]; ItemsList.Clear(); TipViewItems.Clear(); Group* allTipsGroup = new Group(this); @@ -606,18 +607,15 @@ void TipsMenu::SwitchToTipId(int id) { CurrentTipPage = 1; TipsSystem::SetTipUnreadState(id, false); TipsSystem::SetTipNewState(id, false); - auto tipsScriptBufferId = TipsSystem::GetTipsScriptBufferId(); + auto [buffers, tipsScriptBufferId] = TipsSystem::GetTipsScriptBufferCtx(); auto tipRecord = TipsSystem::GetTipRecord(id); - Name->SetText(Vm::BufferOffsetContext{.ScriptBufferId = tipsScriptBufferId, - .IpOffset = tipRecord->StringAdr[0]}, - {0.0f, 0.0f}, NameFontSize, RendererOutlineMode::BottomRight, - DefaultColorIndex); - Pronunciation->SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScriptBufferId, - .IpOffset = tipRecord->StringAdr[1]}, - {0.0f, 0.0f}, PronunciationFontSize, RendererOutlineMode::BottomRight, - DefaultColorIndex); + auto nameStr = TipsSystem::GetTextStringStream(id, 0); + auto pronunciationStr = TipsSystem::GetTextStringStream(id, 1); + Name->SetText(nameStr, {0.0f, 0.0f}, NameFontSize, + RendererOutlineMode::BottomRight, DefaultColorIndex); + Pronunciation->SetText(pronunciationStr, {0.0f, 0.0f}, PronunciationFontSize, + RendererOutlineMode::BottomRight, DefaultColorIndex); // Right alignment Name->MoveTo(NameInitialBounds.GetPos() - glm::vec2{Name->Bounds.Width, 0.0f}); @@ -635,8 +633,9 @@ void TipsMenu::SwitchToTipId(int id) { TextPage.Clear(); Vm::Sc3VmThread dummy; - dummy.IpOffset = tipRecord->StringAdr[3]; dummy.ScriptBufferId = tipsScriptBufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + dummy.IpOffset = tipRecord->StringAdr[3]; TextPage.AddString(&dummy); } @@ -662,9 +661,11 @@ void TipsMenu::AdvanceTipPage(TipAdvanceMode mode) { } TextPage.Clear(); + auto [buffers, tipsScriptBufferId] = TipsSystem::GetTipsScriptBufferCtx(); Vm::Sc3VmThread dummy; + dummy.ScriptBufferId = tipsScriptBufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; dummy.IpOffset = currentRecord->StringAdr[3 + CurrentTipPage - 1]; - dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId(); TextPage.AddString(&dummy); CurrentPage->SetSprite(CurrentPageSprites[CurrentTipPage]); } diff --git a/src/games/chlcc/tipssystem.cpp b/src/games/chlcc/tipssystem.cpp index b73a020e8..94899ec4b 100644 --- a/src/games/chlcc/tipssystem.cpp +++ b/src/games/chlcc/tipssystem.cpp @@ -6,6 +6,7 @@ #include "../../profile/data/tipssystem.h" #include "../../profile/games/chlcc/tipsmenu.h" #include "../../profile/charset.h" +#include "../../profile/vm.h" #include "../../ui/ui.h" namespace Impacto { @@ -21,24 +22,21 @@ void TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr, ScriptBufferId = (uint8_t)scriptBufferId; const auto scriptBuffer = ScriptBuffers[scriptBufferId]; - const std::unordered_map strIndicesMap = [&] { - std::unordered_map sc3Map; - auto [scrBufId, offset] = + const std::unordered_map strIndicesMap = [&] { + std::unordered_map sc3Map; + auto [buffers, bufferId, offset] = ScriptGetTextTableStrAddress(TipsStringTable, SortStringIndex); - - auto sortStr = &ScriptBuffers[scrBufId][offset]; - size_t i = 0; int distance = 0; - while (sortStr[i] != 0xFF) { - if (sortStr[i] & 0x80) { - uint16_t currentSc3Char = - SDL_Swap16BE(UnalignedRead(sortStr + i)); - i += 2; - sc3Map.try_emplace(currentSc3Char, distance++); + Sc3Stream sortStream(&buffers[bufferId][offset]); + while (sortStream.PeekU8() != 0xff) { + StringToken token; + token.Read(sortStream); + if (token.Type == STT_Character) { + sc3Map[token.Val_Int] = distance++; } else { - ImpLogSlow(LogLevel::Error, LogChannel::VM, - "TipsSorter: SC3 Tag Found in Sort String\n", sortStr[i]); - i++; + ImpLogSlow(LogLevel::Warning, LogChannel::VM, + "TipsComparator: SC3 Tag Found in Sort String\n", + token.Type); } } return sc3Map; @@ -48,6 +46,8 @@ void TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr, MemoryStream stream = MemoryStream(&scriptBuffer[tipsDataAdr], tipsDataSize, false); uint16_t numberOfContentStrings = ReadLE(&stream); + auto& buffers = Profile::Vm::UseMsbStrings ? Impacto::Vm::MsbBuffers + : Impacto::Vm::ScriptBuffers; while (numberOfContentStrings != 255) { if (TipEntryCount >= MaxTipsCount) { ImpLog(LogLevel::Error, LogChannel::VM, "Too many tips in tips data\n"); @@ -63,12 +63,22 @@ void TipsSystem::DataInit(uint32_t scriptBufferId, uint32_t tipsDataAdr, }; ReadLE(&stream); // Reads in a padding space string for (uint16_t i = 0; i < record.NumberOfContentStrings + 3; i++) { - record.StringAdr[i] = - ScriptGetStrAddress(scriptBufferId, ReadLE(&stream)); + uint32_t stringId = Profile::Vm::StringIdSize == 4 + ? ReadLE(&stream) + : ReadLE(&stream); + + record.StringAdr[i] = Profile::Vm::UseMsbStrings + ? MsbGetStrAddress(scriptBufferId, stringId) + : ScriptGetStrAddress(scriptBufferId, stringId); } auto sortStrIndex = record.StringAdr[2]; - auto firstChar = SDL_Swap16BE( - UnalignedRead(&ScriptBuffers[scriptBufferId][sortStrIndex])); + const uint32_t firstChar = [&] { + Sc3Stream charStream(&buffers[scriptBufferId][sortStrIndex]); + StringToken t; + t.Read(charStream); + return t.Val_Int; + }(); + auto sortIndexItr = strIndicesMap.find(firstChar); assert(sortIndexItr != strIndicesMap.end()); record.CategoryLetterIndex = diff --git a/src/games/darling/sysmesbox.cpp b/src/games/darling/sysmesbox.cpp index 8f19fcbb8..fe7a1465b 100644 --- a/src/games/darling/sysmesbox.cpp +++ b/src/games/darling/sysmesbox.cpp @@ -170,7 +170,7 @@ void SysMesBox::Init() { void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Messages[MessageCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, @@ -182,7 +182,7 @@ void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { void SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Choices[ChoiceCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, diff --git a/src/games/mo6tw/clearlistmenu.cpp b/src/games/mo6tw/clearlistmenu.cpp index bc9d1cfc1..8f85c70ef 100644 --- a/src/games/mo6tw/clearlistmenu.cpp +++ b/src/games/mo6tw/clearlistmenu.cpp @@ -199,7 +199,7 @@ void ClearListMenu::InitMainPage() { auto separator = Vm::ScriptGetTextTableStrAddress(SeparatorTable, SeparatorEntry); dummy.IpOffset = separator.IpOffset; - dummy.ScriptBufferId = separator.ScriptBufferId; + dummy.ScriptBufferId = separator.BufferId; SeparatorWidth = TextGetPlainLineWidth(&dummy, *Profile::Dialogue::DialogueFont, FontSize); diff --git a/src/games/mo6tw/sysmesbox.cpp b/src/games/mo6tw/sysmesbox.cpp index f4d75dcf3..3bba0b996 100644 --- a/src/games/mo6tw/sysmesbox.cpp +++ b/src/games/mo6tw/sysmesbox.cpp @@ -173,7 +173,7 @@ void SysMesBox::Init() { void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Messages[MessageCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, @@ -185,7 +185,7 @@ void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { void SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Choices[ChoiceCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[0], 1.0f, diff --git a/src/games/mo6tw/tipsmenu.cpp b/src/games/mo6tw/tipsmenu.cpp index 8fecaca22..6f25011eb 100644 --- a/src/games/mo6tw/tipsmenu.cpp +++ b/src/games/mo6tw/tipsmenu.cpp @@ -9,6 +9,7 @@ #include "../../profile/ui/backlogmenu.h" #include "../../profile/ui/tipsmenu.h" #include "../../profile/games/mo6tw/tipsmenu.h" +#include "../../profile/vm.h" #include "../../ui/widgets/mo6tw/tipsentrybutton.h" #include "../../data/tipssystem.h" #include "../../vm/interface/input.h" @@ -142,9 +143,9 @@ void TipsMenu::Init() { int currentPage = 0, currentCategoryId = -1; // String of characters by which tips are sorted, taken from _system script - auto [scriptBufId, sortStrAddr] = + auto [buffers, bufferId, sortStrAddr] = Vm::ScriptGetTextTableStrAddress(SortStringTable, SortStringIndex); - uint8_t* sortString = &Vm::ScriptBuffers[scriptBufId][sortStrAddr]; + uint8_t* sortString = &buffers[bufferId][sortStrAddr]; auto recordCount = TipsSystem::GetTipCount(); float currentY = TipListInitialY; @@ -232,15 +233,17 @@ void TipsMenu::SwitchToTipId(int id) { CurrentTipPage = 1; TipsSystem::SetTipUnreadState(id, false); TipsSystem::SetTipNewState(id, false); - auto tipsScriptBufferId = TipsSystem::GetTipsScriptBufferId(); + auto [buffers, tipsScriptBufferId] = TipsSystem::GetTipsScriptBufferCtx(); auto tipRecord = TipsSystem::GetTipRecord(id); - Name->SetText(Vm::BufferOffsetContext{.ScriptBufferId = tipsScriptBufferId, + Name->SetText(Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScriptBufferId, .IpOffset = tipRecord->StringAdr[0]}, NameInitialBounds.GetPos(), NameFontSize, RendererOutlineMode::Full, DefaultColorIndex); Pronunciation->SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScriptBufferId, + Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScriptBufferId, .IpOffset = tipRecord->StringAdr[1]}, PronunciationInitialBounds.GetPos(), PronunciationFontSize, RendererOutlineMode::Full, DefaultColorIndex); @@ -248,10 +251,13 @@ void TipsMenu::SwitchToTipId(int id) { Vm::Sc3VmThread dummy; dummy.IpOffset = tipRecord->StringAdr[2]; dummy.ScriptBufferId = tipsScriptBufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; float categoryWidth = TextGetPlainLineWidth( &dummy, *Profile::Dialogue::DialogueFont, CategoryFontSize); + Category->Bounds.X = CategoryEndX - categoryWidth; Category->SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScriptBufferId, + Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScriptBufferId, .IpOffset = tipRecord->StringAdr[2]}, {CategoryEndX - categoryWidth, CategoryInitialBounds.Y}, CategoryFontSize, RendererOutlineMode::Full, DefaultColorIndex); @@ -301,8 +307,8 @@ void TipsMenu::AdvanceTipPage(TipAdvanceMode mode) { TextPage.Clear(); Vm::Sc3VmThread dummy; + dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferCtx().BufferId; dummy.IpOffset = currentRecord->StringAdr[2 + CurrentTipPage]; - dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId(); TextPage.AddString(&dummy); CurrentPage->SetText(fmt::to_string(CurrentTipPage), CurrentPageBounds.GetPos(), PageSeparatorFontSize, diff --git a/src/games/rne/sysmesbox.cpp b/src/games/rne/sysmesbox.cpp index 7b55c65a8..433a90b5e 100644 --- a/src/games/rne/sysmesbox.cpp +++ b/src/games/rne/sysmesbox.cpp @@ -271,7 +271,7 @@ void SysMesBox::Init() { void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Messages[MessageCount] = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, Profile::Dialogue::ColorTable[10], 1.0f, diff --git a/src/hud/cclcc/tipsnotification.cpp b/src/hud/cclcc/tipsnotification.cpp index fd12e7fb9..aa806dac8 100644 --- a/src/hud/cclcc/tipsnotification.cpp +++ b/src/hud/cclcc/tipsnotification.cpp @@ -52,8 +52,9 @@ void TipsNotification::Update(float dt) { auto UpdateNotificationDisplay = [&]() { auto tipNameAdr = NotificationQueue.front(); - auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId(); - TipName.SetText(Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, + auto [buffers, tipsScrBufId] = TipsSystem::GetTipsScriptBufferCtx(); + TipName.SetText(Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScrBufId, .IpOffset = tipNameAdr}, {NotificationPositionX, 0.0f}, FontSize, RendererOutlineMode::BottomRight, TipNameColor); diff --git a/src/hud/chlcc/tipsnotification.cpp b/src/hud/chlcc/tipsnotification.cpp index efea662f4..61c95864b 100644 --- a/src/hud/chlcc/tipsnotification.cpp +++ b/src/hud/chlcc/tipsnotification.cpp @@ -61,9 +61,10 @@ void TipsNotification::Update(const float dt) { !NotificationQueue.empty()) { // Start display animation const auto tipNameAdr = NotificationQueue.front(); - const auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId(); + const auto [buffers, tipsScrBufId] = TipsSystem::GetTipsScriptBufferCtx(); TipName.SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, + Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScrBufId, .IpOffset = tipNameAdr}, TextStartPosition + glm::vec2(TextPartBefore.Bounds.Width, 0.0f), TextFontSize, RendererOutlineMode::BottomRight, diff --git a/src/hud/mo6tw/tipsnotification.cpp b/src/hud/mo6tw/tipsnotification.cpp index 29f2a99d3..5ed39ee24 100644 --- a/src/hud/mo6tw/tipsnotification.cpp +++ b/src/hud/mo6tw/tipsnotification.cpp @@ -72,13 +72,17 @@ void TipsNotification::Update(float dt) { if (FadeAnimation.IsIn() && Timer.IsOut()) { Timer.StartIn(); auto tipNameAdr = NotificationQueue.front(); - auto tipsScrBufId = TipsSystem::GetTipsScriptBufferId(); + auto [buffers, tipsScrBufId] = TipsSystem::GetTipsScriptBufferCtx(); TipName->SetText( - Vm::BufferOffsetContext{.ScriptBufferId = tipsScrBufId, + Vm::BufferOffsetContext{.Buffers = buffers, + .BufferId = tipsScrBufId, .IpOffset = tipNameAdr}, {FinalNotificationPosition.x + TextPartBefore->Bounds.Width, FinalNotificationPosition.y}, FontSize, RendererOutlineMode::Full, TipNameColorIndex); + TipName->MoveTo( + glm::vec2(FinalNotificationPosition.x + TextPartBefore->Bounds.Width, + FinalNotificationPosition.y)); TextPartAfter->MoveTo(glm::vec2(FinalNotificationPosition.x + TextPartBefore->Bounds.Width + TipName->Bounds.Width, diff --git a/src/io/fsfolderarchive.cpp b/src/io/fsfolderarchive.cpp index 4baf30f66..3bf941fb1 100644 --- a/src/io/fsfolderarchive.cpp +++ b/src/io/fsfolderarchive.cpp @@ -7,10 +7,21 @@ #include "../util.h" #include #include +#include +#ifndef IMPACTO_DISABLE_MMAP +#include "memorymappedfilestream.h" +#else +#include "physicalfilestream.h" +#endif +#include "textarchive.h" namespace Impacto { namespace Io { +struct TextMetaEntry : FileMeta { + std::string FullPath; +}; + IoError FSFolderArchive::Open(FileMeta* file, Stream** outStream) { TextMetaEntry* entry = (TextMetaEntry*)file; IoError err = PhysicalFileStream::Create(entry->FullPath, outStream); @@ -37,18 +48,8 @@ IoError FSFolderArchive::GetCurrentSize(FileMeta* file, int64_t& outSize) { return IoError_OK; } -IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive) { - namespace fs = std::filesystem; - - ImpLog(LogLevel::Trace, LogChannel::IO, - "Trying to mount \"{:s}\" as filesystem folder archive\n", - stream->Meta.FileName); - - FSFolderArchive* result; - - result = new FSFolderArchive; - result->BaseStream = stream; - +IoError FSFolderArchive::SortTOCLexicographically( + FSFolderArchive* result, std::filesystem::path const& rootPath) { const auto pathLessComparator = [](TextMetaEntry const& a, TextMetaEntry const& b) { const auto toLowerPath = [](std::string p) { @@ -56,12 +57,73 @@ IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive) { if (c >= 'A' && c <= 'Z') return static_cast(std::tolower(c)); return static_cast(c); }); - return fs::path(p); + return std::filesystem::path(p); }; return toLowerPath(a.FullPath) < toLowerPath(b.FullPath); }; - auto iterateDirectory = - [result](auto&& self, std::filesystem::path folderPath) -> IoError { + + std::sort(result->TOC.begin(), result->TOC.end(), pathLessComparator); + return IoError_OK; +} + +IoError FSFolderArchive::SortTOCByOrderFile(FSFolderArchive* result, + VfsArchive** archive) { + TextArchive* txtArch = dynamic_cast(*archive); + if (!txtArch) { + ImpLog(LogLevel::Error, LogChannel::IO, + "SortTOCByOrderFile: order file is not a valid TextArchive\n"); + return IoError_Fail; + } + + std::unordered_map nameToEntry; + nameToEntry.reserve(result->TOC.size()); + for (auto& entry : result->TOC) { + std::string stem = std::filesystem::path(entry.FileName).stem().string(); + nameToEntry[stem] = &entry; + } + + std::vector orderedToc; + orderedToc.reserve(txtArch->IdsToFiles.size()); + + for (auto const& [id, fileMeta] : txtArch->IdsToFiles) { + if (!fileMeta) continue; + + auto foundIt = nameToEntry.find(fileMeta->FileName); + if (foundIt == nameToEntry.end()) continue; + + orderedToc.push_back(*foundIt->second); + } + + result->TOC = std::move(orderedToc); + return IoError_OK; +} + +IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive, + std::optional params) { + namespace fs = std::filesystem; + + ImpLog(LogLevel::Trace, LogChannel::IO, + "Trying to mount \"{:s}\" as filesystem folder archive\n", + stream->Meta.FileName); + + FSFolderArchive* result = new FSFolderArchive; + result->BaseStream = stream; + std::optional whitelistRegex; + + if (params && params->WhitelistPattern) { + try { + whitelistRegex.emplace(*params->WhitelistPattern, + std::regex::ECMAScript | std::regex::icase); + } catch (std::regex_error const& e) { + ImpLog(LogLevel::Error, LogChannel::IO, + "Invalid whitelist pattern \"{:s}\": {:s}\n", + *params->WhitelistPattern, e.what()); + return IoError_Fail; + } + } + + auto iterateDirectory = [result, &whitelistRegex]( + auto&& self, fs::path folderPath) -> IoError { std::error_code ec; for (const auto& dirEntry : fs::directory_iterator(folderPath, ec)) { if (dirEntry.is_directory()) { @@ -72,9 +134,15 @@ IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive) { } const auto path = dirEntry.path(); + std::string fullPath = path.string(); + + if (whitelistRegex && !std::regex_match(fullPath, *whitelistRegex)) { + continue; + } + ec = std::error_code{}; TextMetaEntry entry; - entry.FullPath = path.string(); + entry.FullPath = fullPath; entry.FileName = path.filename().string(); entry.Size = dirEntry.file_size(ec); if (ec) entry.Size = -1; @@ -84,7 +152,6 @@ IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive) { ImpLog(LogLevel::Error, LogChannel::IO, "Failed to iterate over directory {} with error {}\n", folderPath, ec.message()); - if (result) delete result; return IoError_Fail; } return IoError_OK; @@ -93,7 +160,37 @@ IoError FSFolderArchive::Create(Stream* stream, VfsArchive** outArchive) { code != IoError_OK) { return code; } - std::sort(result->TOC.begin(), result->TOC.end(), pathLessComparator); + + if (params && params->OrderFilePath) { + Stream* orderFile; + IoError err; + std::string const& orderFileName = *(params->OrderFilePath); +#ifndef IMPACTO_DISABLE_MMAP + err = MemoryMappedFileStream::Create(orderFileName, + &orderFile); +#else + err = PhysicalFileStream::Create(orderFileName, &orderFile); +#endif + if (err != IoError_OK) { + ImpLog(LogLevel::Debug, LogChannel::IO, + "Could not open physical file \"{:s}\"\n", orderFileName); + delete result; + return err; + } + + VfsArchive* orderFileArchive = nullptr; + TextArchive::Create(orderFile, &orderFileArchive); + if (auto code = SortTOCByOrderFile(result, &orderFileArchive); + code != IoError_OK) { + delete result; + return code; + } + } else if (auto code = + SortTOCLexicographically(result, stream->Meta.FileName); + code != IoError_OK) { + delete result; + return code; + } uint32_t id = 0; for (auto&& entry : result->TOC) { diff --git a/src/io/fsfolderarchive.h b/src/io/fsfolderarchive.h index 24824bac6..d6ada41bc 100644 --- a/src/io/fsfolderarchive.h +++ b/src/io/fsfolderarchive.h @@ -7,8 +7,11 @@ namespace Impacto { namespace Io { -struct TextMetaEntry : FileMeta { - std::string FullPath; +struct TextMetaEntry; + +struct FolderArchiveParameters { + std::optional OrderFilePath; + std::optional WhitelistPattern; }; class FSFolderArchive : public VfsArchive { @@ -16,9 +19,14 @@ class FSFolderArchive : public VfsArchive { IoError Open(FileMeta* file, Stream** outStream) override; IoError GetCurrentSize(FileMeta* file, int64_t& outSize) override; - static IoError Create(Stream* stream, VfsArchive** outArchive); + static IoError Create(Stream* stream, VfsArchive** outArchive, + std::optional params); private: + static IoError SortTOCLexicographically( + FSFolderArchive* result, std::filesystem::path const& rootPath); + static IoError SortTOCByOrderFile(FSFolderArchive* result, + VfsArchive** orderLines); std::vector TOC; }; diff --git a/src/io/textarchive.cpp b/src/io/textarchive.cpp index 981458c4d..f7929a9b4 100644 --- a/src/io/textarchive.cpp +++ b/src/io/textarchive.cpp @@ -80,9 +80,19 @@ IoError TextArchive::Create(Stream* stream, VfsArchive** outArchive) { goto fail; } - content.resize(stream->Meta.Size); - size = stream->Read(&content[0], stream->Meta.Size); - content.resize(size); + if (Io::ReadU8(stream) == 0xEF && Io::ReadU8(stream) == 0xBB && + Io::ReadU8(stream) == 0xBF) { + // UTF-8 BOM + content.resize(stream->Meta.Size - 3); + size = stream->Read(&content[0], stream->Meta.Size - 3); + content.resize(size); + } else { + stream->Seek(0, IoSeek::Set); + content.resize(stream->Meta.Size); + size = stream->Read(&content[0], stream->Meta.Size); + content.resize(size); + } + maxFileCount = std::count(content.begin(), content.end(), '\n') + 1; result = new TextArchive; @@ -126,13 +136,16 @@ IoError TextArchive::Create(Stream* stream, VfsArchive** outArchive) { result->TOC[lineId].FullPath = basePath + line; } else if (type == MLP) { + // fullPath // fullPath,id size_t firstColLength = line.find(','); if (firstColLength == std::string::npos || - firstColLength == line.length() - 1) - continue; + firstColLength == line.length() - 1) { + id = lineId; + } else { + id = std::atoi(&line[firstColLength + 1]); + } std::string fullPath = line.substr(0, firstColLength); - id = std::atoi(&line[firstColLength + 1]); size_t fileNameStart = fullPath.rfind('/') + 1; size_t fileNameStart2 = fullPath.rfind('\\') + 1; if (fileNameStart2 > fileNameStart) fileNameStart = fileNameStart2; diff --git a/src/io/vfs.cpp b/src/io/vfs.cpp index 3d85ddc5e..9da1b0a0e 100644 --- a/src/io/vfs.cpp +++ b/src/io/vfs.cpp @@ -20,7 +20,6 @@ #include "lnk4archive.h" #include "mpkarchive.h" #include "textarchive.h" -#include "fsfolderarchive.h" namespace Impacto { namespace Io { @@ -31,6 +30,8 @@ concept FileId = std::convertible_to || using VfsArchiveFactory = auto (*)(Stream* stream, VfsArchive** outArchive) -> IoError; +using VfsArchiveFactoryFn = + std::function; static std::vector Archivers; @@ -48,7 +49,7 @@ static std::shared_mutex Lock; static IoError MountInternal(std::string const& mountpoint, Stream* stream, bool invertMountOrder = false, - VfsArchiveFactory hintArchiver = nullptr) { + VfsArchiveFactoryFn hintArchiver = nullptr) { VfsArchive* archive = nullptr; IoError err = IoError_Fail; @@ -96,7 +97,8 @@ void VfsInit() { } IoError VfsMount(std::string const& mountpoint, - std::string const& archiveFileName) { + std::string const& archiveFileName, + std::optional parameters) { ImpLog(LogLevel::Debug, LogChannel::IO, "Trying to mount \"{:s}\" on mountpoint \"{:s}\"\n", archiveFileName, mountpoint); @@ -118,10 +120,26 @@ IoError VfsMount(std::string const& mountpoint, ImpLog(LogLevel::Error, LogChannel::IO, "Failed to check if {} is a directory: {}\n", archiveFileName, ec.message()); + + if (parameters && !isDir) { + ImpLog(LogLevel::Warning, LogChannel::IO, + "Folder archive params for \"{:s}\" are specified, but it is not a " + "directory, " + "parameters will be ignored\n", + archiveFileName); + } + if (isDir) { DummyStream::Create(archiveFileName, &archiveFile); - err = - MountInternal(mountpoint, archiveFile, false, &FSFolderArchive::Create); + FolderArchiveParameters params = + parameters.value_or(FolderArchiveParameters{}); + + VfsArchiveFactoryFn factory = [params](Stream* stream, + VfsArchive** outArchive) { + return FSFolderArchive::Create(stream, outArchive, params); + }; + + err = MountInternal(mountpoint, archiveFile, false, factory); } else { #ifndef IMPACTO_DISABLE_MMAP err = MemoryMappedFileStream::Create(archiveFileName, diff --git a/src/io/vfs.h b/src/io/vfs.h index 2441b6b65..3d73216e8 100644 --- a/src/io/vfs.h +++ b/src/io/vfs.h @@ -9,6 +9,7 @@ #include "stream.h" #include #include +#include "fsfolderarchive.h" namespace Impacto { namespace Io { @@ -19,8 +20,10 @@ void VfsInit(); // Mount an archive from a physical file. // Archives with the same name can be mounted several times, files will always // be loaded from the latest-mounted archive they're found in -IoError VfsMount(std::string const& mountpoint, - std::string const& archiveFileName); +// in case other parameters can be provided, extend with std::variant +IoError VfsMount( + std::string const& mountpoint, std::string const& archiveFileName, + std::optional parameters = std::nullopt); // Mount an archive from memory. A unique filename must be specified to identify // files coming from this archive and to unmount it. IoError VfsMountMemory(std::string const& mountpoint, diff --git a/src/profile/data/savesystem.cpp b/src/profile/data/savesystem.cpp index 37c443223..afd6bf989 100644 --- a/src/profile/data/savesystem.cpp +++ b/src/profile/data/savesystem.cpp @@ -4,6 +4,8 @@ #include "../../renderer/renderer.h" #include "../../games/cclcc/savesystem.h" +#include "../../games/cclcc/savesystem_ps4.h" +#include "../../games/cclcc/savesystem_switch.h" #include "../../games/chlcc/savesystem.h" #include "../../games/mo6tw/savesystem.h" @@ -118,8 +120,14 @@ void Configure() { case SaveDataType::MO6TW: Implementation = new Impacto::MO6TW::SaveSystem(); break; - case SaveDataType::CCLCC: - Implementation = new Impacto::CCLCC::SaveSystem(); + case SaveDataType::CCLCC_PS4: + Implementation = new Impacto::CCLCC_PS4::SaveSystem(); + break; + case SaveDataType::CCLCC_SWITCH: + SystemDataPath = EnsureGetMember("SystemDataPath"); + QuickDataPath = EnsureGetMember("QuickDataPath"); + + Implementation = new Impacto::CCLCC_Switch::SaveSystem(); break; case SaveDataType::None: ImpLog(LogLevel::Warning, LogChannel::Profile, diff --git a/src/profile/data/savesystem.h b/src/profile/data/savesystem.h index e67b1ddc0..be81b370f 100644 --- a/src/profile/data/savesystem.h +++ b/src/profile/data/savesystem.h @@ -15,6 +15,10 @@ int constexpr MaxCGSprites = 4; inline Impacto::SaveSystem::SaveDataType Type = Impacto::SaveSystem::SaveDataType::None; +// used for split save files of LCC DP +inline std::optional SystemDataPath; +inline std::optional QuickDataPath; + inline std::string SaveFilePath; inline std::optional ThumbnailFilePath; inline std::vector StoryScriptIDs; diff --git a/src/profile/dialogue.h b/src/profile/dialogue.h index 44e8a78e0..647ec8098 100644 --- a/src/profile/dialogue.h +++ b/src/profile/dialogue.h @@ -34,7 +34,7 @@ namespace Dialogue { using namespace Impacto::Fonts; -constexpr int NO_NAME = 0xffff; +constexpr uint32_t NO_NAME = 0xffffffff; inline Sprite ADVBoxSprite; inline glm::vec2 ADVBoxPos; diff --git a/src/profile/games/cclcc/librarymenu.cpp b/src/profile/games/cclcc/librarymenu.cpp index b7d26358a..3e2a8dabd 100644 --- a/src/profile/games/cclcc/librarymenu.cpp +++ b/src/profile/games/cclcc/librarymenu.cpp @@ -177,6 +177,7 @@ void Configure() { MovieDiskExtraOpHighlight = EnsureGetMember("MovieDiskExtraOpHighlight"); MovieDiskExtraOpPlayId = EnsureGetMember("MovieDiskExtraOpPlayId"); + LibraryMenuId = EnsureGetMember("LibraryMenuId"); } AlbumMenuGuideSprite = EnsureGetMember("AlbumMenuGuideSprite"); diff --git a/src/profile/games/cclcc/librarymenu.h b/src/profile/games/cclcc/librarymenu.h index c1a658182..4d4336eda 100644 --- a/src/profile/games/cclcc/librarymenu.h +++ b/src/profile/games/cclcc/librarymenu.h @@ -112,6 +112,7 @@ inline bool MovieExtraVideosEnabled; inline Sprite MovieDiskExtraOp; inline Sprite MovieDiskExtraOpHighlight; inline int MovieDiskExtraOpPlayId; +inline int LibraryMenuId; inline Sprite AlbumMenuGuideSprite; inline Sprite AlbumMenuCGViewerGuideSprite; diff --git a/src/profile/games/cclcc/titlemenu.cpp b/src/profile/games/cclcc/titlemenu.cpp index 497fafeb5..816a5b0d2 100644 --- a/src/profile/games/cclcc/titlemenu.cpp +++ b/src/profile/games/cclcc/titlemenu.cpp @@ -73,6 +73,15 @@ void Configure() { EnsureGetMember("HighlightAnimationDurationOut"); ExtraDisabledTint = EnsureGetMember("ExtraDisabledTint"); + NewButtonId = EnsureGetMember("NewButtonId"); + LoadButtonId = EnsureGetMember("LoadButtonId"); + QLoadButtonId = EnsureGetMember("QLoadButtonId"); + TipsButtonId = EnsureGetMember("TipsButtonId"); + LibraryButtonId = EnsureGetMember("LibraryButtonId"); + EndingListButtonId = EnsureGetMember("EndingListButtonId"); + ConfigButtonId = EnsureGetMember("ConfigButtonId"); + HelpButtonId = EnsureGetMember("HelpButtonId"); + UI::TitleMenuPtr = new UI::CCLCC::TitleMenu(); auto drawType = EnsureGetMember("DrawType"); UI::Menus[drawType].push_back(UI::TitleMenuPtr); diff --git a/src/profile/games/cclcc/titlemenu.h b/src/profile/games/cclcc/titlemenu.h index df3391550..59d0e2b7d 100644 --- a/src/profile/games/cclcc/titlemenu.h +++ b/src/profile/games/cclcc/titlemenu.h @@ -51,6 +51,15 @@ inline int TitleAnimationStartFrame; inline int TitleAnimationFrameCount; inline int TitleAnimationFileId; +inline int NewButtonId; +inline int LoadButtonId; +inline int QLoadButtonId; +inline int TipsButtonId; +inline int LibraryButtonId; +inline int EndingListButtonId; +inline int ConfigButtonId; +inline int HelpButtonId; + void Configure(); } // namespace TitleMenu diff --git a/src/profile/profile.cpp b/src/profile/profile.cpp index e2d1a9d33..3efad4ec7 100644 --- a/src/profile/profile.cpp +++ b/src/profile/profile.cpp @@ -16,6 +16,7 @@ #include "subtitle.h" #include "game.h" #include "patch.h" +#include "vm.h" #include "../text/textpage.h" #include "../text/text.h" #include "../game.h" @@ -157,7 +158,7 @@ static void DefineEnums() { DefineEnum(LuaState); DefineEnum(LuaState); DefineEnum(LuaState); - DefineEnum(LuaState); + DefineEnum(LuaState); DefineEnum(LuaState); DefineEnum(LuaState); DefineEnum(LuaState); @@ -207,6 +208,7 @@ static void DefineEnums() { DefineEnum(LuaState); DefineEnum(LuaState); DefineEnum(LuaState); + DefineEnum(LuaState); } void Init() { diff --git a/src/profile/profile_internal.h b/src/profile/profile_internal.h index 4fb0a087d..10aaeecf7 100644 --- a/src/profile/profile_internal.h +++ b/src/profile/profile_internal.h @@ -551,6 +551,18 @@ struct TryGetImpl { static std::optional Call(); }; +template <> +struct TryGetImpl { + static std::optional Call() { + if (!lua_istable(LuaState, -1)) return std::nullopt; + + Io::FolderArchiveParameters result; + result.OrderFilePath = TryGetMember("Order"); + result.WhitelistPattern = TryGetMember("Whitelist"); + return result; + } +}; + template inline void GetArray(std::span out) { size_t actualCount = static_cast(lua_rawlen(LuaState, -1)); diff --git a/src/profile/vfs.cpp b/src/profile/vfs.cpp index c2d61c918..1244ac3bf 100644 --- a/src/profile/vfs.cpp +++ b/src/profile/vfs.cpp @@ -1,6 +1,7 @@ #include "vfs.h" #include "profile_internal.h" #include "../io/vfs.h" +#include "../io/fsfolderarchive.h" namespace Impacto { namespace Profile { @@ -12,8 +13,24 @@ void Configure() { EnsurePushMemberOfType("Mounts", LUA_TTABLE); ForEachProfileTable([](std::string const& name) { ForEachProfileArray([&name](size_t index) { - std::string file(EnsureGetArrayElement()); - Io::VfsMount(name, file); + if (lua_istable(LuaState, -1)) { + auto path = TryGetMember("Path"); + if (!path) { + std::string error = fmt::format( + "Mount table for \"{:s}\" (index {:d}) is missing \"Path\"", + name, index); + ImpLog(LogLevel::Fatal, LogChannel::Profile, "{:s}\n", error); + throw std::runtime_error(error); + } + + std::optional parameters = + EnsureGetArrayElement(); + + Io::VfsMount(name, *path, parameters); + } else { + std::string file(EnsureGetArrayElement()); + Io::VfsMount(name, file); + } }); }); Pop(); diff --git a/src/profile/vm.cpp b/src/profile/vm.cpp index adfb90149..6162e5ecb 100644 --- a/src/profile/vm.cpp +++ b/src/profile/vm.cpp @@ -19,6 +19,9 @@ void Configure() { TryGetMember("UseMsbStrings", UseMsbStrings); TryGetMember("UseSeparateMsbArchive", UseSeparateMsbArchive); TryGetMember("RestartMaskUsesThreadAlpha", RestartMaskUsesThreadAlpha); + TryGetMember("StringEncodingType", + StringEncodingType); + TryGetMember("StringIdSize", StringIdSize); ScrWorkChaStructSize = EnsureGetMember("ScrWorkChaStructSize"); ScrWorkChaOffsetStructSize = diff --git a/src/profile/vm.h b/src/profile/vm.h index f3daee433..93585f001 100644 --- a/src/profile/vm.h +++ b/src/profile/vm.h @@ -7,6 +7,11 @@ namespace Impacto { namespace Profile { namespace Vm { +enum class StringUnitEncoding : uint8_t { + Uint16, + Uint32, +}; + inline uint32_t StartScript; inline uint32_t StartScriptBuffer; @@ -34,6 +39,9 @@ inline int SystemScriptBuffer = 1; inline int SpeakerPortraitsScrWorkOffset = 8; +inline StringUnitEncoding StringEncodingType = StringUnitEncoding::Uint16; +inline uint8_t StringIdSize = 2; + void Configure(); } // namespace Vm diff --git a/src/renderer/dx9/renderer.cpp b/src/renderer/dx9/renderer.cpp index 39b57279e..0bfbf4754 100644 --- a/src/renderer/dx9/renderer.cpp +++ b/src/renderer/dx9/renderer.cpp @@ -220,6 +220,8 @@ uint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width, case TexFmt_U8: imageSize = width * height; imageFormat = D3DFMT_A8; + default: { + } } IDirect3DTexture9* texture; diff --git a/src/renderer/opengl/renderer.cpp b/src/renderer/opengl/renderer.cpp index 5c84156ce..6507a331c 100644 --- a/src/renderer/opengl/renderer.cpp +++ b/src/renderer/opengl/renderer.cpp @@ -281,6 +281,13 @@ uint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width, case TexFmt_U8: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); return {GL_R8, GL_RED}; + case TexFmt_UA8: + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_GREEN); + return {GL_RG8, GL_RG}; default: throw std::invalid_argument( diff --git a/src/renderer/vulkan/renderer.cpp b/src/renderer/vulkan/renderer.cpp index 4056e0ba5..1301ec772 100644 --- a/src/renderer/vulkan/renderer.cpp +++ b/src/renderer/vulkan/renderer.cpp @@ -1028,6 +1028,8 @@ uint32_t Renderer::SubmitTexture(TexFmt format, uint8_t* buffer, int width, case TexFmt_U8: imageSize = width * height; imageFormat = VK_FORMAT_R8_UNORM; + default: { + } } AllocatedBuffer stagingBuffer = CreateBuffer( diff --git a/src/scriptvars.h b/src/scriptvars.h index 6aeefebc5..2e7fd7fb7 100644 --- a/src/scriptvars.h +++ b/src/scriptvars.h @@ -37,6 +37,12 @@ V(SW_SYSTEMMENUALPHA) V(SW_SYSMENUCT) V(SW_SYSMENUALPHA) V(SW_SYSSUBMENUCT) +V(SW_SYSSUBMENUCTMAX) // appeared in the LCC switch +V(SW_SYSMENUCTMAX) // appeared in the LCC switch +V(SW_PIC_ARCHIVENO1) // appeared in the LCC switch +V(SW_PIC_REQ_ARCHIVENO1) // appeared in the LCC switch +V(SW_PIC_FILENO1) // appeared in the LCC switch +V(SW_PIC_REQ_FILENO1) // appeared in the LCC switch V(SW_SYSSUBMENUNO) V(SW_SYSSUBMENUALPHA) V(SW_CLRALPHA) @@ -117,6 +123,9 @@ V(SW_DELUSION_LIMIT) V(SW_DELUSION_SPIN_COUNTER) V(SW_DELUSION_PRI) V(SW_YESNO_PRI) +V(SW_YESNO_SELECTED) +V(SW_YESNO_CT) +V(SW_YESNO_TARGET_INDEX) V(SW_CHA1SURF) V(SW_FACE1SURF) V(SW_SHORTCUT) @@ -331,6 +340,7 @@ V(SW_MOVIEALPHA3) V(SW_MOVIEPRI4) V(SW_MOVIEALPHA4) V(SW_MOVIEFRAME) +V(SW_MOVIE_SHADER) V(SW_MOVIETOTALFRAME) V(SW_POKECON_OFSX) V(SW_POKECON_OFSY) @@ -374,6 +384,18 @@ V(SW_UI_BTNGUIDE_REQ) V(SW_UI_BTNGUIDE_PROG) V(SW_UI_BTNGUIDE_TYPE) V(SW_ATCHAN_SCROLL_MAX) +V(SW_MAP_ZOOM) +V(SW_MAP_POS_X) +V(SW_MAP_POS_Y) +V(SW_MAP_DST_POS_X) +V(SW_MAP_DST_POS_Y) +V(SW_MAP_SELECTED_POOL_INDEX) +V(SW_MAP_SELECTED_POOL_ID) +V(SW_MAP_SELECTED_POOL_TYPE) +V(SW_MAP_PHOTO_SELECT_STATE) +V(SW_MAP_RENDER_ALPHA_1) +V(SW_MAP_RENDER_ALPHA_2) +V(SW_MAP_RENDER_ALPHA_3) // FlagWork V(SF_CLR_FLAG) @@ -395,6 +417,9 @@ V(SF_SHOWWAITICON) V(SF_LOADING) V(SF_BG1LOADEXEC) V(SF_SAVEDISABLE) +V(SF_SAVE_UNK1) +V(SF_SAVE_UNK2) +V(SF_SAVE_UNK3) V(SF_AUTOSAVEENABLE) V(SF_MESREVDISABLE) V(SF_MESSAVEPOINT_SSP) @@ -427,6 +452,12 @@ V(SF_CHA1BGEFFECT) V(SF_Phone_Open) V(SF_DATEDISPLAY) V(SF_IRUOENABLE) +V(SF_MAPENABLE) +V(SF_MAP_RENDER_UNK1) +V(SF_MAP_RENDER_UNK2) +V(SF_MAP_RENDER_UNK3) +V(SF_MAP_RENDER_UNK4) +V(SF_MAP_NO_CLAMP) V(SF_AR_SETUP_ADD_MDLBUF1) V(SF_IRUOAUTO) V(SF_IRUO) diff --git a/src/text/dialoguepage.cpp b/src/text/dialoguepage.cpp index 9513932af..83819345c 100644 --- a/src/text/dialoguepage.cpp +++ b/src/text/dialoguepage.cpp @@ -81,7 +81,9 @@ void DialoguePage::Clear() { void DialoguePage::AddString(Vm::Sc3VmThread* ctx, std::optional voiceId, bool acted, int animId, int charId) { - CurrentStringAddress = {ctx->ScriptBufferId, ctx->IpOffset}; + CurrentStringAddress = { + Profile::Vm::UseMsbStrings ? Vm::MsbBuffers : Vm::ScriptBuffers, + ctx->ScriptBufferId, ctx->IpOffset}; AudioId = voiceId; CharacterId = charId; @@ -93,7 +95,8 @@ void DialoguePage::AddString(Vm::Sc3VmThread* ctx, std::optional voiceId, DialogueTextParserInst.ParseString(*this, ctx); - RenderName = ScrWork[SW_MESNAMEID0 + Id] != NO_NAME || !Name.empty(); + RenderName = + ScrWork[SW_MESNAMEID0 + Id] != static_cast(NO_NAME) || !Name.empty(); // resetting typewriter for a new line and setting new params Typewriter.Reset(AnimationDirection::In); @@ -189,7 +192,7 @@ void DialoguePage::Render(const float alpha, if (windowIsVisible) { const NameInfo nameInfo{ .RenderWindow = RenderName, - .NameId = ScrWork[SW_MESNAMEID0 + Id] == NO_NAME + .NameId = ScrWork[SW_MESNAMEID0 + Id] == static_cast(NO_NAME) ? std::nullopt : std::optional(ScrWork[SW_MESNAMEID0 + Id]), .Name = Name, diff --git a/src/text/fonts/font.h b/src/text/fonts/font.h index 2adb45cde..dfee74217 100644 --- a/src/text/fonts/font.h +++ b/src/text/fonts/font.h @@ -130,8 +130,8 @@ class SingleSheetFont : public Font { private: Sprite GetGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / GridSize.x); - const uint8_t col = static_cast(id % GridSize.x); + const uint16_t row = static_cast(id / GridSize.x); + const uint16_t col = static_cast(id % GridSize.x); const float width = AdvanceWidths[id]; return Sprite(Sheet, col * CellSize.x + 1.0f, row * CellSize.y + 1.0f, @@ -203,8 +203,8 @@ class SeparateOutlineSheetFont : public Font { private: Sprite GetGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / ForegroundGridSize.x); - const uint8_t col = static_cast(id % ForegroundGridSize.x); + const uint16_t row = static_cast(id / ForegroundGridSize.x); + const uint16_t col = static_cast(id % ForegroundGridSize.x); const float width = AdvanceWidths[id]; return Sprite(ForegroundSheet, col * ForegroundCellSize.x + 1.0f, @@ -213,8 +213,8 @@ class SeparateOutlineSheetFont : public Font { } Sprite GetOutlineGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / OutlineGridSize.x); - const uint8_t col = static_cast(id % OutlineGridSize.x); + const uint16_t row = static_cast(id / OutlineGridSize.x); + const uint16_t col = static_cast(id % OutlineGridSize.x); const float width = AdvanceWidths[id]; return Sprite(OutlineSheet, col * OutlineCellSize.x + 1.0f, @@ -249,8 +249,8 @@ class LanguageBarrierFont final : public SeparateOutlineSheetFont { private: Sprite GetGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / ForegroundGridSize.x); - const uint8_t col = static_cast(id % ForegroundGridSize.x); + const uint16_t row = static_cast(id / ForegroundGridSize.x); + const uint16_t col = static_cast(id % ForegroundGridSize.x); return Sprite(ForegroundSheet, col * ForegroundCellSize.x, row * ForegroundCellSize.y, ForegroundCellSize.x, @@ -258,8 +258,8 @@ class LanguageBarrierFont final : public SeparateOutlineSheetFont { } Sprite GetOutlineGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / OutlineGridSize.x); - const uint8_t col = static_cast(id % OutlineGridSize.x); + const uint16_t row = static_cast(id / OutlineGridSize.x); + const uint16_t col = static_cast(id % OutlineGridSize.x); return Sprite(OutlineSheet, col * OutlineCellSize.x, row * OutlineCellSize.y, OutlineCellSize.x, @@ -293,8 +293,8 @@ class EdgeDetectedSingleSheetFont final : public SingleSheetFont { private: Sprite GetGlyph(uint32_t id) const { - const uint8_t row = static_cast(id / GridSize.x); - const uint8_t col = static_cast(id % GridSize.x); + const uint16_t row = static_cast(id / GridSize.x); + const uint16_t col = static_cast(id % GridSize.x); const float width = AdvanceWidths[id]; return Sprite(Sheet, col * CellSize.x, row * CellSize.y, width, diff --git a/src/text/text.cpp b/src/text/text.cpp index c235b8960..f1649b42b 100644 --- a/src/text/text.cpp +++ b/src/text/text.cpp @@ -27,7 +27,7 @@ int StringToken::Read(Vm::Sc3VmThread* ctx) { int bytesRead = 0; Flags = 0; - uint8_t c = *ctx->GetIp(); + uint8_t c = *ctx->GetStringIp(); ctx->IpOffset++; bytesRead++; switch (c) { @@ -57,7 +57,7 @@ int StringToken::Read(Vm::Sc3VmThread* ctx) { case STT_GetHardcodedValue: case STT_UnlockTip: { Type = (StringTokenType)c; - Val_Uint16 = (*ctx->GetIp() << 8) | *(ctx->GetIp() + 1); + Val_Int = (*ctx->GetStringIp() << 8) | *(ctx->GetStringIp() + 1); ctx->IpOffset += 2; bytesRead += 2; break; @@ -66,13 +66,13 @@ int StringToken::Read(Vm::Sc3VmThread* ctx) { case STT_SetColor: { Type = (StringTokenType)c; if (ColorTagIsUint8) { - Val_Expr = (*(uint8_t*)(ctx->GetIp())); + Val_Expr = (*(uint8_t*)(ctx->GetStringIp())); ctx->IpOffset += 1; bytesRead += 1; } else { uint32_t oldIp = ctx->IpOffset; // TODO is this really okay to do in parsing code? - Val_Expr = Vm::ExpressionEval(ctx); + Val_Expr = Vm::ExpressionEval(ctx, true); bytesRead += (int)(ctx->IpOffset - oldIp); } break; @@ -82,7 +82,7 @@ int StringToken::Read(Vm::Sc3VmThread* ctx) { Type = (StringTokenType)c; uint32_t oldIp = ctx->IpOffset; // TODO is this really okay to do in parsing code? - Val_Expr = Vm::ExpressionEval(ctx); + Val_Expr = Vm::ExpressionEval(ctx, true); bytesRead += (int)(ctx->IpOffset - oldIp); break; } @@ -97,13 +97,21 @@ int StringToken::Read(Vm::Sc3VmThread* ctx) { "Encountered unrecognized token 0x{:02x} in string\n", c); Type = STT_EndOfString; } else { - uint16_t glyphId = (((uint16_t)c & 0x7F) << 8) | *ctx->GetIp(); + uint16_t glyphId = (((uint16_t)c & 0x7F) << 8) | *ctx->GetStringIp(); ctx->IpOffset++; Flags |= GetFlags(glyphId); Type = STT_Character; - Val_Uint16 = glyphId; + Val_Int = glyphId; + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + Val_Int = (Val_Int << 16); + Val_Int |= *ctx->GetStringIp() << 8; + ctx->IpOffset++; + Val_Int |= *ctx->GetStringIp(); + ctx->IpOffset++; + } } break; } @@ -125,9 +133,15 @@ int StringToken::Read(Vm::Sc3Stream& stream) { } else if (c == STT_EndOfString) { Type = STT_EndOfString; } else { - uint16_t glyphId = (((uint16_t)c & 0x7F) << 8) | stream.ReadU8(); Type = STT_Character; - Val_Uint16 = glyphId; + Val_Int = (((uint16_t)c & 0x7F) << 8) | stream.ReadU8(); + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + Val_Int <<= 16; + Val_Int |= (uint32_t)stream.ReadU8() << 8; + Val_Int |= (uint32_t)stream.ReadU8(); + return 4; + } return 2; } return 1; @@ -135,13 +149,11 @@ int StringToken::Read(Vm::Sc3Stream& stream) { void StringToken::AddFlags(const Vm::BufferOffsetContext scrCtx, const uint8_t flags) { - Vm::Sc3VmThread dummy; - dummy.ScriptBufferId = scrCtx.ScriptBufferId; - dummy.IpOffset = scrCtx.IpOffset; + Vm::Sc3Stream stream = &scrCtx.Buffers[scrCtx.BufferId][scrCtx.IpOffset]; StringToken token; - token.Read(&dummy); - for (; token.Type != STT_EndOfString; token.Read(&dummy)) { + token.Read(stream); + for (; token.Type != STT_EndOfString; token.Read(stream)) { if (token.Type != STT_Character) { ImpLog(LogLevel::Error, LogChannel::VM, "Encountered non-character token 0x{:02x} in flag string\n", @@ -149,10 +161,16 @@ void StringToken::AddFlags(const Vm::BufferOffsetContext scrCtx, return; } - AddFlags(token.Val_Uint16, flags); + AddFlags(token.Val_Int, flags); } } +uint16_t StringToken::GetValU16() const { + assert(Type != STT_Character || Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint16); + return static_cast(Val_Int); +} + [[nodiscard]] static size_t TextGetStringLength(Sc3Type auto&& stream) { size_t result = 0; StringToken token; @@ -193,7 +211,7 @@ size_t TextLayoutPlainLine(Sc3Type auto&& stream, } break; case STT_Character: { - const uint32_t glyphId = token.Val_Uint16; + const uint32_t glyphId = token.Val_Int; *curGlyph = font.PlaceGlyph(glyphId, {currentX, pos.y}, fontSize, currentColors, opacity); @@ -218,6 +236,7 @@ template std::vector TextLayoutPlainLine( template std::vector TextLayoutPlainLine( Vm::Sc3VmThread*&&, std::optional, const Font&, float, DialogueColorPair, float, glm::vec2, TextAlignment); + std::vector TextLayoutPlainLine( Sc3Type auto&& stream, std::optional maxLength, const Font& font, const float fontSize, const DialogueColorPair colors, const float opacity, @@ -284,8 +303,8 @@ float TextGetPlainLineWidth(Sc3Type auto&& stream, const Font& font, if (token.Type == STT_EndOfString) break; if (token.Type != STT_Character) continue; - width += (fontSize / font.BitmapEmWidth) * - font.GetAdvanceWidth(token.Val_Uint16); + width += + (fontSize / font.BitmapEmWidth) * font.GetAdvanceWidth(token.Val_Int); } return width; @@ -331,30 +350,55 @@ size_t TextLayoutPlainString(const std::string_view str, const TextAlignment alignment) { size_t sc3StrLength = utf8::distance(str.begin(), str.end()) + 1; assert(outGlyphs.size() == sc3StrLength - 1); - std::vector sc3Str(sc3StrLength); + auto layout = [&]() { + std::unique_ptr sc3StrPtr(new T[sc3StrLength]); + + TextGetSc3String( + str, std::span(sc3StrPtr.get(), sc3StrPtr.get() + sc3StrLength)); - TextGetSc3String(str, sc3Str); + Vm::Sc3Stream stream(sc3StrPtr.get()); + return TextLayoutPlainLine(stream, outGlyphs, font, fontSize, colors, + opacity, pos, alignment); + }; - Vm::Sc3Stream stream(sc3Str.data()); - return TextLayoutPlainLine(stream, outGlyphs, font, fontSize, colors, opacity, - pos, alignment); + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + return layout.template operator()(); + } else { + return layout.template operator()(); + } } std::vector TextLayoutPlainString( const std::string_view str, const Font& font, const float fontSize, const DialogueColorPair colors, const float opacity, const glm::vec2 pos, const TextAlignment alignment) { - const size_t stringLength = utf8::distance(str.begin(), str.end()); - std::vector outGlyphs(stringLength); + std::string_view::iterator strIt = str.begin(); + std::string_view::iterator strEnd = str.end(); - TextLayoutPlainString(str, outGlyphs, font, fontSize, colors, opacity, pos, - alignment); + int sc3StrLength = (int)utf8::distance(strIt, strEnd) + 1; + auto layout = [&]() { + std::unique_ptr sc3StrPtr(new T[sc3StrLength]); - return outGlyphs; + TextGetSc3String( + str, std::span(sc3StrPtr.get(), sc3StrPtr.get() + sc3StrLength)); + + Vm::Sc3Stream stream(sc3StrPtr.get()); + return TextLayoutPlainLine(stream, sc3StrLength, font, fontSize, colors, + opacity, pos, alignment); + }; + + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + return layout.template operator()(); + } else { + return layout.template operator()(); + } } -void TextGetSc3String(const std::string_view str, - const std::span out) { +template + requires std::same_as || std::same_as +void TextGetSc3String(std::string_view str, std::span out) { std::string_view::iterator strIt = str.begin(); std::string_view::iterator strEnd = str.end(); @@ -365,29 +409,75 @@ void TextGetSc3String(const std::string_view str, const auto codePoint = utf8::next(strIt, strEnd); const uint16_t sc3Val = Profile::Charset::CharacterToSc3[codePoint]; - out[sc3Idx++] = SDL_Swap16(sc3Val); + + if constexpr (std::is_same_v) { + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + // rebuild 16 bit LE to 32bit BE + uint8_t hiByte = ((sc3Val - 0x8000u) >> 8) & 0xFF; + uint8_t loByte = sc3Val & 0xFF; + uint32_t res = static_cast(0x80) | + (static_cast(hiByte) << 16) | + (static_cast(loByte) << 24); + out[sc3Idx++] = res; + } else { + out[sc3Idx++] = SDL_Swap16(sc3Val); + } + } else { + out[sc3Idx++] = SDL_Swap16(sc3Val); + } } out[sc3Idx++] = 0xFF; } +template void TextGetSc3String(std::string_view, std::span); +template void TextGetSc3String(std::string_view, std::span); + void InitNamePlateData(Vm::Sc3Stream& stream) { do { uint16_t id = stream.ReadU16(); - uint16_t stringId = stream.ReadU16(); + uint32_t stringId; + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint16) { + stringId = stream.ReadU16(); + } else { + stringId = stream.ReadU32(); + } + uint32_t nameAddr = - Vm::ScriptGetStrAddress(Profile::Vm::SystemScriptBuffer, stringId); + Profile::Vm::UseMsbStrings + ? Vm::MsbGetStrAddress(Profile::Vm::SystemScriptBuffer, stringId) + : Vm::ScriptGetStrAddress(Profile::Vm::SystemScriptBuffer, + stringId); Vm::Sc3VmThread dummy; dummy.IpOffset = nameAddr; dummy.ScriptBufferId = Profile::Vm::SystemScriptBuffer; - size_t nameLength = (TextGetStringLength(&dummy) - 1) * 2; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + size_t nameLength = (TextGetStringLength(&dummy) - 1); dummy.IpOffset = nameAddr; - uint32_t nameHash = - GetHashCode(std::span(dummy.GetIp(), nameLength)); + + std::vector vec; + std::span spanned; + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint16) { + vec.reserve(nameLength); + auto ptr = std::span( + reinterpret_cast(dummy.GetStringIp()), nameLength); + for (auto num : ptr) { + vec.push_back(static_cast(num)); + } + spanned = std::span( + reinterpret_cast(vec.data()), nameLength * 4); + } else { + spanned = std::span(dummy.GetStringIp(), nameLength * 4); + } + + uint32_t nameHash = GetHashCode(spanned); NamePlateData[nameHash] = id; } while (stream.PeekU16() != 0xFFFF); } -std::optional GetNameId(const std::span name) { +std::optional GetNameId(const std::span name) { uint32_t nameHash = GetHashCode(std::span( std::bit_cast(name.data()), name.size_bytes())); if (NamePlateData.find(nameHash) != NamePlateData.end()) diff --git a/src/text/text.h b/src/text/text.h index 3d4d094b6..afa64d45a 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -64,7 +64,7 @@ struct StringToken { public: StringTokenType Type; - uint16_t Val_Uint16; + uint32_t Val_Int; int Val_Expr; uint8_t Flags{}; @@ -86,6 +86,8 @@ struct StringToken { int Read(Vm::Sc3VmThread* ctx); int Read(Vm::Sc3Stream& stream); + uint16_t GetValU16() const; + private: static inline ankerl::unordered_dense::map FlagsMap; }; @@ -129,11 +131,31 @@ size_t TextLayoutPlainString(const std::string_view str, DialogueColorPair colors, float opacity, glm::vec2 pos, TextAlignment alignment); -void TextGetSc3String(const std::string_view str, std::span out); +template + requires std::same_as || std::same_as +void TextGetSc3String(std::string_view str, std::span out); + +template + requires std::same_as || std::same_as +void TextGetSc3String(std::string_view str, std::array& out) { + TextGetSc3String(str, std::span(out)); +} + +template + requires std::same_as || std::same_as +void TextGetSc3String(std::string_view str, std::vector& out) { + TextGetSc3String(str, std::span(out)); +} + +template + requires std::same_as || std::same_as +void TextGetSc3String(std::string_view str, T (&out)[N]) { + TextGetSc3String(str, std::span(out)); +} inline ankerl::unordered_dense::map NamePlateData; void InitNamePlateData(Vm::Sc3Stream& stream); -std::optional GetNameId(std::span name); +std::optional GetNameId(std::span name); } // namespace Impacto diff --git a/src/text/textpage.h b/src/text/textpage.h index d85e4186f..dd6d021ba 100644 --- a/src/text/textpage.h +++ b/src/text/textpage.h @@ -10,7 +10,7 @@ struct RubyChunk { size_t FirstBaseCharacter = 0; size_t BaseLength = 0; std::vector Text; - std::vector RawText; + std::vector RawText; bool CenterPerCharacter = false; void FinishBase(size_t glyphCount) { diff --git a/src/text/textparser.cpp b/src/text/textparser.cpp index 57050dfb6..6f6d2eea4 100644 --- a/src/text/textparser.cpp +++ b/src/text/textparser.cpp @@ -103,7 +103,7 @@ void TextParser::ParseStringToken(const StringToken& token) { template <> void TextParser::ParseStringToken(const StringToken& token) { - FontSize = ModeInfo.TextGlyphSize.y * (token.Val_Uint16 / SetFontSizeRatio); + FontSize = ModeInfo.TextGlyphSize.y * (token.Val_Int / SetFontSizeRatio); } template <> @@ -119,12 +119,12 @@ void TextParser::ParseStringToken(const StringToken& token) { template <> void TextParser::ParseStringToken(const StringToken& token) { - CurrentLineTopMargin = token.Val_Uint16; + CurrentLineTopMargin = static_cast(token.Val_Int); } template <> void TextParser::ParseStringToken(const StringToken& token) { - float addX = token.Val_Uint16; + float addX = static_cast(token.Val_Int); if (CurrentX + addX > ModeInfo.MaxLineWidth) { FinishLine(Glyphs.size()); addX -= (ModeInfo.MaxLineWidth - CurrentX); @@ -139,11 +139,12 @@ void TextParser::ParseStringToken(const StringToken& token) { template <> void TextParser::ParseStringToken(const StringToken& token) { - if (!TipsSystem::GetTipLockedState(token.Val_Uint16)) return; + if (!TipsSystem::GetTipLockedState(token.Val_Int)) return; - TipsSystem::SetTipLockedState(token.Val_Uint16, false); - TipsNotification::AddTip(token.Val_Uint16); - TipsSystem::GetNewTipsIndices().push_back(token.Val_Uint16); + TipsSystem::SetTipLockedState(token.Val_Int, false); + TipsNotification::AddTip(token.Val_Int); + TipsSystem::GetNewTipsIndices().push_back( + static_cast(token.Val_Int)); } template <> @@ -172,14 +173,27 @@ template <> void TextParser::ParseStringToken(const StringToken& token) { switch (ParsingState) { case TextParsingState::Name: { - NameCode.back() = SDL_Swap16(token.Val_Uint16 | 0x8000); + uint32_t val{}; + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + val = SDL_Swap32(token.Val_Int | 0x80000000); + } else { + val = SDL_Swap16(static_cast(token.Val_Int) | 0x8000); + } + NameCode.back() = val; NameCode.emplace_back(STT_EndOfString); return; } case TextParsingState::RubyAnnotation: { - RubyChunks.back().RawText.push_back( - SDL_Swap16(token.Val_Uint16 | 0x8000)); + uint32_t val{}; + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint32) { + val = SDL_Swap32(token.Val_Int | 0x80000000); + } else { + val = SDL_Swap16(static_cast(token.Val_Int) | 0x8000); + } + RubyChunks.back().RawText.push_back(val); return; } @@ -187,7 +201,7 @@ void TextParser::ParseStringToken(const StringToken& token) { case TextParsingState::RubyBase: { // TODO respect TA_Center // TODO what to do about left margin if text alignment is center? - const uint32_t glyphId = token.Val_Uint16; + const uint32_t glyphId = token.Val_Int; const auto& glyph = Glyphs.emplace_back(DialogueFont->PlaceGlyph( glyphId, {ModeInfo.WindowPos.x + CurrentX, 0.0f}, FontSize, CurrentColors, 1.0f)); @@ -380,7 +394,22 @@ void TextParser::FinishName() { return; } - Vm::Sc3Stream nameStream(NameCode.data()); + Vm::Sc3Stream nameStream(nullptr); + std::vector name16bit; + + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint16) { + name16bit.reserve(NameCode.size()); + std::transform(NameCode.begin(), NameCode.end(), + std::back_inserter(name16bit), [](const uint32_t& elem) { + return static_cast(elem & 0xFFFF); + }); + + nameStream = Vm::Sc3Stream(name16bit.data()); + } else { + nameStream = Vm::Sc3Stream(NameCode.data()); + } + const float nameWidth = TextGetPlainLineWidth(nameStream, *DialogueFont, ModeInfo.NameGlyphSize.y); @@ -453,7 +482,12 @@ void TextParser::FinishName() { break; } - nameStream = Vm::Sc3Stream(NameCode.data()); + if (Profile::Vm::StringEncodingType == + Profile::Vm::StringUnitEncoding::Uint16) { + nameStream = Vm::Sc3Stream(name16bit.data()); + } else { + nameStream = Vm::Sc3Stream(NameCode.data()); + } Name = TextLayoutPlainLine(nameStream, NameCode.size() - 1, *DialogueFont, ModeInfo.NameGlyphSize.y, ColorTable[0], 1.0f, pos, TextAlignment::Left); diff --git a/src/text/textparser.h b/src/text/textparser.h index bafe774f9..c372ad0fd 100644 --- a/src/text/textparser.h +++ b/src/text/textparser.h @@ -39,7 +39,7 @@ class TextParser { void FinishLine(size_t nextLineStart, bool force = false); void FinishName(); - std::vector NameCode; + std::vector NameCode; enum class TextParsingState { Normal, Name, RubyBase, RubyAnnotation }; TextParsingState ParsingState = TextParsingState::Normal; diff --git a/src/texture/stbiloader.cpp b/src/texture/stbiloader.cpp index a732d26b8..5cd84da21 100644 --- a/src/texture/stbiloader.cpp +++ b/src/texture/stbiloader.cpp @@ -34,6 +34,10 @@ bool TextureLoadSTBI(Io::Stream* stream, Texture* outTexture) { outTexture->Format = TexFmt_U8; break; } + case 2: { + outTexture->Format = TexFmt_UA8; + break; + } case 3: { outTexture->Format = TexFmt_RGB; break; diff --git a/src/texture/texture.cpp b/src/texture/texture.cpp index acda93e1e..cdc627c14 100644 --- a/src/texture/texture.cpp +++ b/src/texture/texture.cpp @@ -42,6 +42,8 @@ void Texture::Init(TexFmt format, int width, int height) { return width * height * 4; case TexFmt_RGB: return width * height * 3; + case TexFmt_UA8: + return width * height * 2; case TexFmt_U8: return width * height; default: diff --git a/src/texture/texture.h b/src/texture/texture.h index 2e69af5f8..add8b127e 100644 --- a/src/texture/texture.h +++ b/src/texture/texture.h @@ -7,7 +7,7 @@ namespace Impacto { class BaseRenderer; -enum TexFmt { TexFmt_RGB, TexFmt_RGBA, TexFmt_U8 }; +enum TexFmt { TexFmt_RGB, TexFmt_RGBA, TexFmt_U8, TexFmt_UA8 }; struct Texture { int Width = 0; diff --git a/src/ui/selectionmenu.cpp b/src/ui/selectionmenu.cpp index cbccb582c..3ad927786 100644 --- a/src/ui/selectionmenu.cpp +++ b/src/ui/selectionmenu.cpp @@ -33,7 +33,7 @@ void SelectionMenu::InitSelectionMenu(bool isPlain) { void SelectionMenu::AddChoice(Vm::BufferOffsetContext ctx) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = ctx.IpOffset; - dummy.ScriptBufferId = ctx.ScriptBufferId; + dummy.ScriptBufferId = ctx.BufferId; Choices[ChoiceCount] = TextLayoutPlainLine( &dummy, 255, *Profile::Dialogue::DialogueFont, Profile::Dialogue::DefaultFontSize, Profile::Dialogue::ColorTable[0], diff --git a/src/ui/widgets/backlogentry.cpp b/src/ui/widgets/backlogentry.cpp index 3addf115b..4b8d95f21 100644 --- a/src/ui/widgets/backlogentry.cpp +++ b/src/ui/widgets/backlogentry.cpp @@ -3,6 +3,7 @@ #include "../../inputsystem.h" #include "../../mem.h" #include "../../profile/scriptvars.h" +#include "../../profile/vm.h" #include "../../vm/interface/input.h" #include "../../vm/thread.h" #include "../../profile/dialogue.h" @@ -23,7 +24,8 @@ BacklogEntry::BacklogEntry(Vm::BufferOffsetContext scrCtx, Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = scrCtx.IpOffset; - dummy.ScriptBufferId = scrCtx.ScriptBufferId; + dummy.ScriptBufferId = scrCtx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; Page.AddString(&dummy); Bounds = GetTextBounds(Page.Glyphs); diff --git a/src/ui/widgets/button.cpp b/src/ui/widgets/button.cpp index 22fe30c63..3ce4ac944 100644 --- a/src/ui/widgets/button.cpp +++ b/src/ui/widgets/button.cpp @@ -5,6 +5,7 @@ #include "../../profile/scriptvars.h" #include "../../vm/interface/input.h" #include "../../vm/thread.h" +#include "../../profile/vm.h" #include "../../profile/dialogue.h" namespace Impacto { @@ -76,7 +77,8 @@ void Button::SetText(Vm::BufferOffsetContext scrCtx, float fontSize, HasText = true; Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = scrCtx.IpOffset; - dummy.ScriptBufferId = scrCtx.ScriptBufferId; + dummy.ScriptBufferId = scrCtx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; Text = TextLayoutPlainLine( &dummy, 255, *Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f, glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left); diff --git a/src/ui/widgets/cclcc/tipsentrybutton.cpp b/src/ui/widgets/cclcc/tipsentrybutton.cpp index 8687aec24..2447bb997 100644 --- a/src/ui/widgets/cclcc/tipsentrybutton.cpp +++ b/src/ui/widgets/cclcc/tipsentrybutton.cpp @@ -3,6 +3,7 @@ #include "../../../renderer/renderer.h" #include "../../../profile/dialogue.h" #include "../../../profile/games/cclcc/tipsmenu.h" +#include "../../../profile/vm.h" #include "../../../text/text.h" #include "../../../vm/vm.h" #include "../../../inputsystem.h" @@ -34,7 +35,8 @@ TipsEntryButton::TipsEntryButton(int tipId, int dispId, RectF const& dest, glm::vec2(Bounds.X, Bounds.Y) + TipsEntryNumberOffset, TextAlignment::Left); Vm::Sc3VmThread dummy; - dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId(); + dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferCtx().BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; glm::vec2 nameDest = glm::vec2(Bounds.X, Bounds.Y) + TipsEntryNameOffset; dummy.IpOffset = TipEntryRecord->StringAdr[1]; HasText = true; @@ -45,11 +47,13 @@ TipsEntryButton::TipsEntryButton(int tipId, int dispId, RectF const& dest, (float)TipsEntryNameFontSize, {initColorName, 0}, 1.0f, nameDest, TextAlignment::Left); - dummy.IpOffset = Vm::ScriptGetStrAddress(TipsSystem::GetTipsScriptBufferId(), - TipsTextEntryLockedIndex); - TextLayoutPlainLine(&dummy, TipLockedText, *Profile::Dialogue::DialogueFont, - (float)TipsEntryNameFontSize, {initColorName, 0}, 1.0f, - nameDest, TextAlignment::Left); + auto [buffers, bufferId, lockedStrAddr] = Vm::ScriptGetTextTableStrAddress( + TipsTextTableIndex, TipsTextEntryLockedIndex); + Vm::Sc3Stream lockedStrStream(&buffers[bufferId][lockedStrAddr]); + TipLockedText = TextLayoutPlainLine( + lockedStrStream, std::nullopt, *Profile::Dialogue::DialogueFont, + (float)TipsEntryNameFontSize, {initColorName, 0}, 1.0f, nameDest, + TextAlignment::Left); HighlightOffset = TipsEntryHighlightOffset; Bounds = dest; HoverBounds = dest; diff --git a/src/ui/widgets/cclcc/tipsentrybutton.h b/src/ui/widgets/cclcc/tipsentrybutton.h index d8187d3fb..127b120be 100644 --- a/src/ui/widgets/cclcc/tipsentrybutton.h +++ b/src/ui/widgets/cclcc/tipsentrybutton.h @@ -26,7 +26,7 @@ class TipsEntryButton : public Widgets::Button { private: std::array TipNumber; - std::array TipLockedText; + std::vector TipLockedText; bool PrevUnreadState; bool IsNewState; }; diff --git a/src/ui/widgets/chlcc/saveentrybutton.cpp b/src/ui/widgets/chlcc/saveentrybutton.cpp index 922486cb5..d089ba6d2 100644 --- a/src/ui/widgets/chlcc/saveentrybutton.cpp +++ b/src/ui/widgets/chlcc/saveentrybutton.cpp @@ -88,7 +88,7 @@ void SaveEntryButton::AddSceneTitleText(Vm::BufferOffsetContext strAdr, glm::vec2 relativeTitlePosition, int colorIndex) { Vm::Sc3VmThread dummy{}; - dummy.ScriptBufferId = strAdr.ScriptBufferId; + dummy.ScriptBufferId = strAdr.BufferId; dummy.IpOffset = strAdr.IpOffset; std::vector text = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, diff --git a/src/ui/widgets/chlcc/tipsentrybutton.cpp b/src/ui/widgets/chlcc/tipsentrybutton.cpp index 7ef93b953..9e7eec49a 100644 --- a/src/ui/widgets/chlcc/tipsentrybutton.cpp +++ b/src/ui/widgets/chlcc/tipsentrybutton.cpp @@ -3,6 +3,7 @@ #include "../../../renderer/renderer.h" #include "../../../profile/dialogue.h" #include "../../../profile/games/chlcc/tipsmenu.h" +#include "../../../profile/vm.h" #include "../../../text/text.h" #include "../../../vm/vm.h" #include "../../../inputsystem.h" @@ -35,7 +36,8 @@ TipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord, Vm::Sc3VmThread dummy; dummy.IpOffset = tipRecord->StringAdr[0]; - dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId(); + dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferCtx().BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; auto textColorIndex = (tipRecord->IsUnread) ? UnreadColorIndex : DefaultColorIndex; Text = TextLayoutPlainLine( @@ -47,10 +49,10 @@ TipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord, auto lockedScrPos = Vm::ScriptGetTextTableStrAddress(TipsStringTable, LockedTipsIndex); - dummy.IpOffset = lockedScrPos.IpOffset; - dummy.ScriptBufferId = lockedScrPos.ScriptBufferId; + Vm::Sc3Stream lockedStrStream( + &lockedScrPos.Buffers[lockedScrPos.BufferId][lockedScrPos.IpOffset]); TextLayoutPlainLine( - &dummy, TipLockedText, *Profile::Dialogue::DialogueFont, + lockedStrStream, TipLockedText, *Profile::Dialogue::DialogueFont, TipListEntryFontSize, Profile::Dialogue::ColorTable[UnreadColorIndex], 1.0f, glm::vec2(Bounds.X + TipListEntryNameXOffset + TipListEntryTextOffsetX, diff --git a/src/ui/widgets/label.cpp b/src/ui/widgets/label.cpp index 07f895f78..d4625d0d6 100644 --- a/src/ui/widgets/label.cpp +++ b/src/ui/widgets/label.cpp @@ -1,5 +1,7 @@ #include "label.h" #include "../../vm/thread.h" +#include "../../profile/dialogue.h" +#include "../../profile/vm.h" #include @@ -87,7 +89,8 @@ void Label::SetText(LabelStringType auto str, glm::vec2 pos, float fontSize, Vm::BufferOffsetContext>) { Impacto::Vm::Sc3VmThread dummy; dummy.IpOffset = str.IpOffset; - dummy.ScriptBufferId = str.ScriptBufferId; + dummy.ScriptBufferId = str.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; text = TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, fontSize, colorPair, 1.0f, glm::vec2(0.0f), TextAlignment::Left); diff --git a/src/ui/widgets/mo6tw/tipsentrybutton.cpp b/src/ui/widgets/mo6tw/tipsentrybutton.cpp index 82a7099bc..08d0e7629 100644 --- a/src/ui/widgets/mo6tw/tipsentrybutton.cpp +++ b/src/ui/widgets/mo6tw/tipsentrybutton.cpp @@ -30,7 +30,7 @@ TipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord, glm::vec2(Bounds.X, Bounds.Y), TextAlignment::Left); Vm::Sc3VmThread dummy; dummy.IpOffset = tipRecord->StringAdr[0]; - dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferId(); + dummy.ScriptBufferId = TipsSystem::GetTipsScriptBufferCtx().BufferId; Text = TextLayoutPlainLine( &dummy, 255, *Profile::Dialogue::DialogueFont, TipListEntryFontSize, Profile::Dialogue::ColorTable[DefaultColorIndex], 1.0f, @@ -44,7 +44,7 @@ TipsEntryButton::TipsEntryButton(int id, TipsDataRecord* tipRecord, auto lockedScrPos = Vm::ScriptGetTextTableStrAddress(TipListEntryLockedTable, TipListEntryLockedIndex); dummy.IpOffset = lockedScrPos.IpOffset; - dummy.ScriptBufferId = lockedScrPos.ScriptBufferId; + dummy.ScriptBufferId = lockedScrPos.BufferId; TipLockedText = TextLayoutPlainLine( &dummy, 255, *Profile::Dialogue::DialogueFont, TipListEntryFontSize, Profile::Dialogue::ColorTable[UnreadColorIndex], 1.0f, diff --git a/src/vm/expression.cpp b/src/vm/expression.cpp index 9ccf1bdf7..fb010013f 100644 --- a/src/vm/expression.cpp +++ b/src/vm/expression.cpp @@ -70,32 +70,42 @@ struct ExprToken { class ExpressionNode { public: ExprTokenType ExprType; + int Value; std::unique_ptr LeftExpr; std::unique_ptr RightExpr; - int Value; - int Evaluate(Sc3VmThread* thd); void AssignValue(Sc3VmThread* thd); }; class ExpressionParser { public: - ExpressionParser(Sc3VmThread* thd); + ExpressionParser(Sc3VmThread* thd, bool isString); ExpressionNode* ParseSubExpression(int minPrecidence); private: - int CurrentToken; std::vector Tokens; + int CurrentToken; + bool IsString; + void GetTokens(Sc3VmThread* thd); ExpressionNode* ParseTerm(); + uint8_t GetIp(Sc3VmThread* thd) const; }; -int ExpressionEval(Sc3VmThread* thd) { +uint8_t ExpressionParser::GetIp(Sc3VmThread* thd) const { + if (IsString) { + return *thd->GetStringIp(); + } else { + return *thd->GetIp(); + } +} + +int ExpressionEval(Sc3VmThread* thd, bool isString) { std::unique_ptr parser = - std::make_unique(thd); + std::make_unique(thd, isString); ExpressionNode* root = parser->ParseSubExpression(0); std::unique_ptr rootPtr = @@ -104,6 +114,32 @@ int ExpressionEval(Sc3VmThread* thd) { return root == nullptr ? 0 : rootPtr->Evaluate(thd); } +int ExpressionSkip(uint8_t* ip) { + uint8_t* ipLocal = ip; + while (*ipLocal != 0) { + if ((*ipLocal & 0x80) == 0) { + ipLocal = ipLocal + 2; + } else { + uint8_t b = *ipLocal & 0x60; + switch (b) { + case 0x00: + ipLocal = ipLocal + 2; + break; + case 0x20: + ipLocal = ipLocal + 3; + break; + case 0x40: + ipLocal = ipLocal + 4; + break; + case 0x60: + ipLocal = ipLocal + 6; + break; + } + } + } + return (int)(ipLocal + 1 - ip); +} + int ExpressionNode::Evaluate(Sc3VmThread* thd) { int leftVal, rightVal; @@ -195,8 +231,15 @@ int ExpressionNode::Evaluate(Sc3VmThread* thd) { break; case ET_ImmediateValue: return Value; - case ET_FuncGlobalVars: - return ScrWork[RightExpr->Evaluate(thd)]; + case ET_FuncGlobalVars: { + int index = RightExpr->Evaluate(thd); + if (index < 0 || index >= ScrWorkSize) { + ImpLog(LogLevel::Warning, LogChannel::Expr, + "Tried to access oob ScrWork[{}]\n", index); + } + index = std::clamp(index, 0, ScrWorkSize); + return ScrWork[index]; + } case ET_FuncFlags: return GetFlag(RightExpr->Evaluate(thd)); case ET_FuncDataAccess: @@ -294,9 +337,14 @@ void ExpressionNode::AssignValue(Sc3VmThread* thd) { int index = LeftExpr->RightExpr->Evaluate(thd); switch (LeftExpr->ExprType) { - case ET_FuncGlobalVars: - ScrWork[index] = leftVal; - break; + case ET_FuncGlobalVars: { + if (index >= 0 && index < ScrWorkSize) { + ScrWork[index] = leftVal; + } else { + ImpLogSlow(LogLevel::Warning, LogChannel::Expr, + "Tried to access oob ScrWork[{}]\n", index); + } + } break; case ET_FuncFlags: SetFlag(index, leftVal); break; @@ -318,7 +366,8 @@ void ExpressionNode::AssignValue(Sc3VmThread* thd) { } } -ExpressionParser::ExpressionParser(Sc3VmThread* thd) { +ExpressionParser::ExpressionParser(Sc3VmThread* thd, bool isString = false) { + this->IsString = isString; GetTokens(thd); CurrentToken = 0; } @@ -443,18 +492,18 @@ ExpressionNode* ExpressionParser::ParseTerm() { void ExpressionParser::GetTokens(Sc3VmThread* thd) { ExprToken curToken; - if (*thd->GetIp()) { + if (GetIp(thd)) { do { - int8_t tokenType = *thd->GetIp(); + int8_t tokenType = GetIp(thd); if (tokenType >= 0) { curToken.Type = (ExprTokenType)tokenType; thd->IpOffset++; - curToken.Precedence = *(thd->GetIp()); + curToken.Precedence = GetIp(thd); thd->IpOffset++; curToken.Value = 0; Tokens.push_back(curToken); } else { - uint8_t* immValue = thd->GetIp(); + uint8_t* immValue = IsString ? thd->GetStringIp() : thd->GetIp(); curToken.Type = ET_ImmediateValue; switch (tokenType & 0x60) { case 0: @@ -482,10 +531,10 @@ void ExpressionParser::GetTokens(Sc3VmThread* thd) { break; } thd->IpOffset++; - curToken.Precedence = *(thd->GetIp()); + curToken.Precedence = GetIp(thd); Tokens.push_back(curToken); } - } while (*thd->GetIp()); + } while (GetIp(thd)); } thd->IpOffset++; diff --git a/src/vm/expression.h b/src/vm/expression.h index 251ad79ea..87799395c 100644 --- a/src/vm/expression.h +++ b/src/vm/expression.h @@ -7,8 +7,8 @@ namespace Impacto { namespace Vm { -int ExpressionEval(Sc3VmThread* thread); - +int ExpressionEval(Sc3VmThread* thread, bool isString = false); +int ExpressionSkip(uint8_t* ip); } // namespace Vm } // namespace Impacto \ No newline at end of file diff --git a/src/vm/inst_controlflow.cpp b/src/vm/inst_controlflow.cpp index bc0146366..791ee75dd 100644 --- a/src/vm/inst_controlflow.cpp +++ b/src/vm/inst_controlflow.cpp @@ -169,6 +169,19 @@ VmInstruction(InstFlagOnJump) { thread->IpOffset = labelAdr; } } + +VmInstruction(InstFlagOnJumpFar) { + StartInstruction; + PopUint8(value); + PopExpression(flagId); + PopExpression(scriptBufferId); + PopFarLabel(labelAdr, scriptBufferId); + + if (GetFlag(flagId) == (bool)value) { + thread->IpOffset = labelAdr; + } +} + VmInstruction(InstKeyOnJump) { using namespace Interface; @@ -244,10 +257,10 @@ VmInstruction(InstClickOnJump) { if (Input::KeyboardButtonWentDown[SDL_SCANCODE_D]) { thread->IpOffset = labelAdr; } - ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, - "STUB instruction ClickOnJump(arg1: {:d}, arg2: {:d}, " - "labelNum: {:d})\n", - arg1, arg2, labelNum); + ImpLog(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction ClickOnJump(arg1: {:d}, arg2: {:d}, " + "labelNum: {:d})\n", + arg1, arg2, labelNum); } VmInstruction(InstKeyboardOnJump) { using namespace Interface; @@ -348,7 +361,6 @@ VmInstruction(InstFlagOffReturn) { } } } - } // namespace Vm } // namespace Impacto \ No newline at end of file diff --git a/src/vm/inst_controlflow.h b/src/vm/inst_controlflow.h index 417e08d61..06ff454f0 100644 --- a/src/vm/inst_controlflow.h +++ b/src/vm/inst_controlflow.h @@ -16,6 +16,7 @@ VmInstruction(InstReturn); VmInstruction(InstReturnIfFlag); VmInstruction(InstLoop); VmInstruction(InstFlagOnJump); +VmInstruction(InstFlagOnJumpFar); VmInstruction(InstKeyOnJump); VmInstruction(InstKeyOnJump_Dash); VmInstruction(InstClickOnJump); diff --git a/src/vm/inst_dialogue.cpp b/src/vm/inst_dialogue.cpp index 2a70ef823..8a167f5d0 100644 --- a/src/vm/inst_dialogue.cpp +++ b/src/vm/inst_dialogue.cpp @@ -280,7 +280,8 @@ VmInstruction(InstMesSetID) { int dialoguePageId = 0; PopUint8(type); - switch (type) { + uint8_t kind = type & 0x7F; + switch (kind) { case 0: { // SetSavePointPage0 if (Profile::Vm::UseReturnIds) { PopUint16(savePointId); @@ -531,7 +532,7 @@ VmInstruction(InstMes) { bool voiced = type & 1; bool acted = type & (1 << 1); bool sync = type & (1 << 3); - bool MSB = type & (1 << 7); + bool msb = type & (1 << 7); std::optional audioId; int animationId = 0; @@ -540,10 +541,16 @@ VmInstruction(InstMes) { if (acted) animationId = ExpressionEval(thread); if (characterId == 32) characterId = 0; - PopUint16(lineId); - const uint32_t line = - MSB ? MsbGetStrAddress(thread->ScriptBufferId, lineId) - : ScriptGetStrAddress(thread->ScriptBufferId, lineId); + int lineId; + uint32_t line; + if (msb) { + lineId = ExpressionEval(thread); + line = MsbGetStrAddress(thread->ScriptBufferId, lineId); + } else { + PopUint16(lineIdTemp); + lineId = lineIdTemp; + line = ScriptGetStrAddress(thread->ScriptBufferId, lineId); + } if (!(ScrWork[Profile::Vm::ScrWorkMesStructSize * thread->DialoguePageId + SW_MESWIN0TYPE] & @@ -566,7 +573,10 @@ VmInstruction(InstMes) { uint32_t oldIp = thread->IpOffset; thread->IpOffset = line; + + thread->UseMSBBuffers = msb; dialoguePage.AddString(thread, audioId, acted, animationId, characterId); + ResetInstruction; if (!GetFlag(SF_MESSAVEPOINT_SSP + thread->DialoguePageId)) { if (!(ScrWork[thread->DialoguePageId * Profile::Vm::ScrWorkMesStructSize + @@ -789,9 +799,9 @@ VmInstruction(InstSetNGmoji) { PopString(endingPuncts); PopString(startingPuncts); - StringToken::AddFlags({thread->ScriptBufferId, startingPuncts}, + StringToken::AddFlags({ScriptBuffers, thread->ScriptBufferId, startingPuncts}, +CharacterTypeFlags::WordStartingPunct); - StringToken::AddFlags({thread->ScriptBufferId, endingPuncts}, + StringToken::AddFlags({ScriptBuffers, thread->ScriptBufferId, endingPuncts}, +CharacterTypeFlags::WordEndingPunct); } VmInstruction(InstMesRev) { @@ -939,6 +949,7 @@ VmInstruction(InstSel) { case 0: { // SelInit if (Profile::Vm::GameInstructionSet == InstructionSet::Dash || Profile::Vm::GameInstructionSet == InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch || Profile::Vm::GameInstructionSet == InstructionSet::MO8) { PopUint16(savepointid); // SF_MESSAVEPOINT_SSP + dialog page's field 5 in decompile? @@ -960,28 +971,32 @@ VmInstruction(InstSel) { case 1: { PopUint16(selStrNum); auto offset = ScriptGetStrAddress(thread->ScriptBufferId, selStrNum); - UI::SelectionMenuPtr->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = offset}); + UI::SelectionMenuPtr->AddChoice({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = offset}); break; } case 0x81: { PopMsbString(line); - UI::SelectionMenuPtr->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line}); + UI::SelectionMenuPtr->AddChoice({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = line}); } break; case 2: { PopUint16(selStrNum); auto offset = ScriptGetStrAddress(thread->ScriptBufferId, selStrNum); - UI::SelectionMenuPtr->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = offset}); + UI::SelectionMenuPtr->AddChoice({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = offset}); PopExpression(arg2); break; } case 0x82: { PopMsbString(line); PopExpression(arg2); - UI::SelectionMenuPtr->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line}); + UI::SelectionMenuPtr->AddChoice({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = line}); } break; } } @@ -1117,12 +1132,13 @@ VmInstruction(InstNameID) { switch (type) { case 0: if (Profile::Vm::GameInstructionSet == InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch || Profile::Vm::GameInstructionSet == InstructionSet::MO8 || Profile::Vm::GameInstructionSet == InstructionSet::CHN) { PopLocalLabel(namePlateDataBlock); Sc3Stream namePlateData( &ScriptBuffers[thread->ScriptBufferId][namePlateDataBlock]); - if (!Profile::Vm::UseMsbStrings) InitNamePlateData(namePlateData); + InitNamePlateData(namePlateData); } else if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW) { PopExpression(arg1); PopExpression(arg2); @@ -1159,14 +1175,15 @@ VmInstruction(InstTips) { uint32_t tipsDataSize = ScriptGetLabelSize(thread->ScriptBufferId, tipsLabelNum); TipsSystem::DataInit(thread->ScriptBufferId, tipsDataAdr, tipsDataSize); - if (Profile::Vm::GameInstructionSet == InstructionSet::CC && - UI::TipsMenuPtr) { + if (Profile::Vm::GameInstructionSet == InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { UI::TipsMenuPtr->Init(); } } break; case 1: // TipsInit TipsSystem::UpdateTipRecords(); - if (Profile::Vm::GameInstructionSet != InstructionSet::CC && + if ((Profile::Vm::GameInstructionSet != InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) && UI::TipsMenuPtr) { UI::TipsMenuPtr->Init(); } @@ -1184,7 +1201,8 @@ VmInstruction(InstTips) { break; case 5: TipsSystem::UpdateTipRecords(); - if (Profile::Vm::GameInstructionSet != InstructionSet::CC && + if ((Profile::Vm::GameInstructionSet != InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) && UI::TipsMenuPtr) { UI::TipsMenuPtr->Init(); } @@ -1221,20 +1239,22 @@ VmInstruction(InstSetRevMes) { } int lineId; + uint32_t line; if (expression) { lineId = ExpressionEval(thread); + line = MsbGetStrAddress(thread->ScriptBufferId, lineId); } else { PopUint16(lineIdTemp); lineId = lineIdTemp; + line = ScriptGetStrAddress(thread->ScriptBufferId, lineId); } - uint32_t line = ScriptGetStrAddress(thread->ScriptBufferId, lineId); uint32_t scriptId = LoadedScriptMetas[thread->ScriptBufferId].Id; SaveSystem::SetLineRead(scriptId, lineId); UI::BacklogMenuPtr->AddMessage( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = line}, audioId, - animationId); + {.Buffers = {}, .BufferId = thread->ScriptBufferId, .IpOffset = line}, + audioId, animationId); } void ChkMesSkip(float dt) { diff --git a/src/vm/inst_gamespecific.cpp b/src/vm/inst_gamespecific.cpp index 1c4cbc02f..d82e55bb0 100644 --- a/src/vm/inst_gamespecific.cpp +++ b/src/vm/inst_gamespecific.cpp @@ -517,7 +517,8 @@ VmInstruction(InstMapSystem) { case 0xA: { PopExpression(arg1); PopExpression(arg2); - inst.MapGetPos(arg1, arg2, ScrWork[6365], ScrWork[6366]); + inst.MapGetPos(arg1, arg2, ScrWork[SW_MAP_DST_POS_X], + ScrWork[SW_MAP_DST_POS_Y]); } break; case 0xB: { @@ -597,8 +598,8 @@ VmInstruction(InstMapSystem) { LogLevel::Warning, LogChannel::VMStub, "STUB instruction MapZoomInit(arg1: {:d}, arg2: {:d}, arg3: {:d})\n", arg1, arg2, arg3); - if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] || - arg3 != ScrWork[6362]) { + if (arg1 != ScrWork[SW_MAP_POS_X] || arg2 != ScrWork[SW_MAP_POS_Y] || + arg3 != ScrWork[SW_MAP_ZOOM]) { inst.MapZoomInit(arg1, arg2, arg3); } else { thread->IpOffset += 3; @@ -620,8 +621,8 @@ VmInstruction(InstMapSystem) { LogLevel::Warning, LogChannel::VMStub, "STUB instruction MapZoomInit2(arg1: {:d}, arg2: {:d}, arg3: {:d})\n", arg1, arg2, arg3); - if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] || - arg3 != ScrWork[6362]) { + if (arg1 != ScrWork[SW_MAP_POS_X] || arg2 != ScrWork[SW_MAP_POS_Y] || + arg3 != ScrWork[SW_MAP_ZOOM]) { inst.MapZoomInit2(arg1, arg2); } else { thread->IpOffset += 3; @@ -637,8 +638,8 @@ VmInstruction(InstMapSystem) { PopExpression(arg1); PopExpression(arg2); PopExpression(arg3); - if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] || - arg3 != ScrWork[6362]) { + if (arg1 != ScrWork[SW_MAP_POS_X] || arg2 != ScrWork[SW_MAP_POS_Y] || + arg3 != ScrWork[SW_MAP_ZOOM]) { if (!inst.MapZoomInit3(arg1, arg2, arg3)) { thread->IpOffset += 3; } @@ -650,8 +651,8 @@ VmInstruction(InstMapSystem) { PopExpression(arg1); PopExpression(arg2); PopExpression(arg3); - if (arg1 != ScrWork[6363] || arg2 != ScrWork[6364] || - arg3 != ScrWork[6362]) { + if (arg1 != ScrWork[SW_MAP_POS_X] || arg2 != ScrWork[SW_MAP_POS_Y] || + arg3 != ScrWork[SW_MAP_ZOOM]) { if (!inst.MapMoveAnimeInit2(arg1, arg2, arg3)) { thread->IpOffset += 3; } @@ -1423,7 +1424,7 @@ VmInstruction(InstMtrg) { SetFlag(SF_MOVIELOADPLAYFL, 0); ScrWork[SW_MOVIE_PLAYNO] = 0xffff; ScrWork[SW_MOVIE_LOADNO] = 0xffff; - ScrWork[6344] = 0; + ScrWork[SW_MOVIE_SHADER] = 0; return; } if (GetFlag(SF_MESALLSKIP)) { @@ -1435,7 +1436,7 @@ VmInstruction(InstMtrg) { SetFlag(SF_MOVIELOADPLAYFL, 0); ScrWork[SW_MOVIE_PLAYNO] = 0xffff; ScrWork[SW_MOVIE_LOADNO] = 0xffff; - ScrWork[6344] = 0; + ScrWork[SW_MOVIE_SHADER] = 0; return; } } break; @@ -1467,6 +1468,42 @@ VmInstruction(InstMtrg) { BlockThread; } +VmInstruction(InstUnk2010CCLCC) { + StartInstruction; + PopUint8(type); + if ((type | 0x20) == 0x20) { + PopExpression(arg1); + } + + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction Unk2010CCLCC(type: {:d})\n", type); +} + +VmInstruction(InstUnk2011CCLCC) { + StartInstruction; + PopUint8(type); + if (type != 0) { + return; + } + + // Show shop app info? + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction Unk2011CCLCC(type: {:d})\n", type); +} + +VmInstruction(InstUnk2012CCLCC) { + StartInstruction; + PopUint8(_); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction Unk2012CCLCC()\n"); +} + +// on switch calls PushOpenUsers and ExecuteProgram, probably platform specific +VmInstruction(InstLaunchApp) { + StartInstruction; + PopExpression(appId); +} + } // namespace Vm } // namespace Impacto \ No newline at end of file diff --git a/src/vm/inst_gamespecific.h b/src/vm/inst_gamespecific.h index b4fdcfb95..d4a7146e4 100644 --- a/src/vm/inst_gamespecific.h +++ b/src/vm/inst_gamespecific.h @@ -34,7 +34,11 @@ VmInstruction(InstTwipo); VmInstruction(InstTwipo_Dash); VmInstruction(InstDelusionTriggerCHLCC); VmInstruction(InstYesNoTriggerCCLCC); +VmInstruction(InstLaunchApp); VmInstruction(InstUnk103A); +VmInstruction(InstUnk2010CCLCC); +VmInstruction(InstUnk2011CCLCC); +VmInstruction(InstUnk2012CCLCC); } // namespace Vm diff --git a/src/vm/inst_graphics2d.cpp b/src/vm/inst_graphics2d.cpp index 1b98c43c2..1ee2e9e7c 100644 --- a/src/vm/inst_graphics2d.cpp +++ b/src/vm/inst_graphics2d.cpp @@ -44,6 +44,24 @@ VmInstruction(InstReleaseSurf) { Renderer->UnloadSurf(surfaceId); } } +VmInstruction(InstReleaseSurfNew) { + StartInstruction; + PopUint8(type); + if (type == 1) { + PopExpression(surfaceId); + ImpLog(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction ReleaseSurf(surfaceId: {:d})\n", surfaceId); + if (surfaceId < 8) { + if (Backgrounds2D[surfaceId]->Status == LoadStatus::Loaded) { + Backgrounds2D[surfaceId]->Unload(); + } + } else { + Renderer->UnloadSurf(surfaceId); + } + } else { + // TODO: delete all surfaces + } +} VmInstruction(InstLoadPic) { StartInstruction; PopExpression(surfaceId); @@ -105,7 +123,7 @@ VmInstruction(InstBGload) { StartInstruction; PopExpression(bufferId); PopExpression(backgroundId); - int actualBufId = GetBufferId(bufferId); + int actualBufId = bufferId > 0 ? GetBufferId(bufferId) : 0; int bgBufId = ScrWork[SW_BG1SURF + actualBufId]; if (Backgrounds2D[bgBufId]->Status == LoadStatus::Loading) { ResetInstruction; @@ -118,6 +136,36 @@ VmInstruction(InstBGload) { BlockThread; } } +VmInstruction(InstBGloadNew) { + StartInstruction; + PopUint8(type); + PopExpression(bufferId); + PopExpression(backgroundId); + if ((type & 0x7f) == 0x10) { + PopExpression(unk01); + } + [[maybe_unused]] + char* name = nullptr; + if (type & 0x80) { + name = reinterpret_cast(thread->GetIp()); + do { + thread->IpOffset++; + } while ((*thread->GetIp()) != '\0'); + thread->IpOffset++; + } + const int actualBufId = GetBufferId(bufferId); + const int bgBufId = ScrWork[SW_BG1SURF + actualBufId]; + if (Backgrounds2D[bgBufId]->Status == LoadStatus::Loading) { + ResetInstruction; + BlockThread; + } else if (ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] != + backgroundId) { + ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] = backgroundId; + Backgrounds2D[bgBufId]->LoadAsync(backgroundId); + ResetInstruction; + BlockThread; + } +} VmInstruction(InstBGswap) { StartInstruction; PopExpression(srcBufferId); diff --git a/src/vm/inst_graphics2d.h b/src/vm/inst_graphics2d.h index 5ea1dd6df..19ff4b333 100644 --- a/src/vm/inst_graphics2d.h +++ b/src/vm/inst_graphics2d.h @@ -8,10 +8,12 @@ namespace Vm { VmInstruction(InstCreateSurf); VmInstruction(InstReleaseSurf); +VmInstruction(InstReleaseSurfNew); VmInstruction(InstLoadPic); VmInstruction(InstSurfFill); VmInstruction(InstSCcapture); VmInstruction(InstBGload); +VmInstruction(InstBGloadNew); VmInstruction(InstBGswap); VmInstruction(InstBGsetColor); VmInstruction(InstBGsetLink); diff --git a/src/vm/inst_macros.inc b/src/vm/inst_macros.inc index e7880cc5a..b9bbac12c 100644 --- a/src/vm/inst_macros.inc +++ b/src/vm/inst_macros.inc @@ -19,6 +19,11 @@ /* Sometimes needed by subsequent code, silence warning */ \ (void)name; \ thread->IpOffset += 2; +#define PopUint32(name) \ + uint32_t name = SDL_SwapLE32(UnalignedRead(thread->GetIp())); \ + /* Sometimes needed by subsequent code, silence warning */ \ + (void)name; \ + thread->IpOffset += 4; #define PopLocalLabel(name) \ uint32_t name; \ { \ @@ -34,6 +39,24 @@ } \ (void)0 #define PopExpression(name) [[maybe_unused]] int name = ExpressionEval(thread) +#define PopStringId(name) \ + uint32_t name; \ + { \ + if (Profile::Vm::StringIdSize == 2) { \ + PopUint16(stringId_); \ + name = stringId_; \ + } else if (Profile::Vm::StringIdSize == 4) { \ + PopUint32(stringId_); \ + name = stringId_; \ + } else { \ + ImpLogSlow(LogLevel::Error, LogChannel::VM, \ + "Unsupported StringIdSize: {:d}\n", \ + Profile::Vm::StringIdSize); \ + name = std::numeric_limits::max(); \ + } \ + } \ + (void)0 + #define PopString(name) \ uint32_t name; \ { \ diff --git a/src/vm/inst_misc.cpp b/src/vm/inst_misc.cpp index d4b873903..c50faf69f 100644 --- a/src/vm/inst_misc.cpp +++ b/src/vm/inst_misc.cpp @@ -5,6 +5,9 @@ #include "expression.h" #include "interface/input.h" #include "../profile/scriptvars.h" +#include "../profile/configsystem.h" +#include "../profile/games/cclcc/systemmenu.h" + #include "../game.h" #include "../mem.h" #include "../log.h" @@ -98,7 +101,8 @@ VmInstruction(InstSystemMenu) { switch (mode) { case 0: if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW) { - } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC) { + } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { auto* sysMenuPtr = static_cast(UI::SystemMenuPtr); sysMenuPtr->Init(); @@ -109,6 +113,9 @@ VmInstruction(InstSystemMenu) { BlockThread; } } + if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { + ScrWork[SW_SYSMENUCTMAX] = 32; + } break; case 1: { @@ -524,29 +531,51 @@ VmInstruction(InstTitleMenuNew) { case 0: // Init ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, "STUB instruction TitleMenu(type: Init)\n"); + switch (Profile::Vm::GameInstructionSet) { + case InstructionSet::LCCSwitch: { + ScrWork[SW_SYSSUBMENUCTMAX] = 32; + } break; + default: { + } + } break; case 1: // Main switch (Profile::Vm::GameInstructionSet) { default: break; + case InstructionSet::LCCSwitch: case InstructionSet::CC: case InstructionSet::CHN: { - using enum UI::CC::TitleMenuMode::Mode; + int mainState; + int startTransitionState; + int pressToStartState; + + if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { + using enum UI::CC::TitleMenuMode::Switch::Mode; + mainState = Main; + pressToStartState = PressToStart; + startTransitionState = StartTransition; + } else { + using enum UI::CC::TitleMenuMode::PS4::Mode; + mainState = Main; + pressToStartState = PressToStart; + startTransitionState = StartTransition; + } - if (ScrWork[SW_TITLEMODE] == Main) { + if (ScrWork[SW_TITLEMODE] == mainState) { if (!UI::TitleMenuPtr->AllowsScriptInput) { ResetInstruction; BlockThread; } - } else if (ScrWork[SW_TITLEMODE] == PressToStart && + } else if (ScrWork[SW_TITLEMODE] == pressToStartState && ScrWork[SW_TITLEDISPCT] == - (Profile::Vm::GameInstructionSet == InstructionSet::CC - ? 60 - : 400)) { + (Profile::Vm::GameInstructionSet == InstructionSet::CHN + ? 400 + : 60)) { // Check "PRESS TO START" here if (((Interface::PADinputButtonWentDown & Interface::PAD1A) || (Interface::PADinputMouseWentDown & Interface::PAD1A))) { - ScrWork[SW_TITLEMODE] = StartTransition; + ScrWork[SW_TITLEMODE] = startTransitionState; ScrWork[SW_TITLEDISPCT] = 0; ScrWork[SW_TITLEMOVIECT] = 0; SetFlag(SF_TITLEEND, 1); @@ -615,7 +644,8 @@ VmInstruction(InstSetPlayMode) { VmInstruction(InstSetEVflag) { StartInstruction; if (Profile::Vm::GameInstructionSet == InstructionSet::MO8 || - Profile::Vm::GameInstructionSet == InstructionSet::CHN) { + Profile::Vm::GameInstructionSet == InstructionSet::CHN || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { PopUint8(unk01); } PopExpression(arg1); @@ -656,6 +686,51 @@ VmInstruction(InstExitGame) { Game::ShouldQuit = true; } +VmInstruction(InstLoadFontMeta) { + StartInstruction; + PopUint8(type); + + PopExpression(unkId); + PopExpression(archiveId); + PopExpression(fileId); + + if (type != 0) { + ExpressionEval(thread); + ExpressionEval(thread); + ExpressionEval(thread); + ExpressionEval(thread); + ExpressionEval(thread); + ExpressionEval(thread); + } + ImpLogSlow( + LogLevel::Warning, LogChannel::VMStub, + "STUB instruction LoadFontMeta(type: {:d}, unkId: {:d}, archiveId: " + "{:d}, fileId: {:d})\n", + type, unkId, archiveId, fileId); +} +VmInstruction(InstSaveIconState) { + StartInstruction; + PopUint8(type); + if (type == 10) { + SetFlag(SF_SAVEICON, 0); + SaveIconDisplay::Hide(); + return; + } + if (type != 1) { + if (!type) { + SetFlag(SF_SAVEICON, 1); + SaveIconDisplay::Show(); + } + return; + } + + SetFlag(SF_SAVEICON, 1); + PopExpression(posX); + PopExpression(posY); + const glm::vec2 pos = {posX, posY}; + SaveIconDisplay::ShowAt(pos); +} + } // namespace Vm } // namespace Impacto diff --git a/src/vm/inst_misc.h b/src/vm/inst_misc.h index 2e7b47c6e..5155ea371 100644 --- a/src/vm/inst_misc.h +++ b/src/vm/inst_misc.h @@ -46,6 +46,8 @@ VmInstruction(InstSetSceneViewFlag); VmInstruction(InstChkClearFlag); VmInstruction(InstScreenChange); VmInstruction(InstExitGame); +VmInstruction(InstLoadFontMeta); +VmInstruction(InstSaveIconState); } // namespace Vm diff --git a/src/vm/inst_sound.cpp b/src/vm/inst_sound.cpp index c979f5c0a..d2109fb09 100644 --- a/src/vm/inst_sound.cpp +++ b/src/vm/inst_sound.cpp @@ -206,7 +206,8 @@ VmInstruction(InstSysVoicePlay) { VmInstruction(InstSysSeload) { StartInstruction; if (Profile::Vm::GameInstructionSet == InstructionSet::MO8 || - Profile::Vm::GameInstructionSet == InstructionSet::CHN) { + Profile::Vm::GameInstructionSet == InstructionSet::CHN || + Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { PopUint8(arg1); } ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, diff --git a/src/vm/inst_system.cpp b/src/vm/inst_system.cpp index 79e1d3c32..48d2b3f24 100644 --- a/src/vm/inst_system.cpp +++ b/src/vm/inst_system.cpp @@ -57,6 +57,37 @@ VmInstruction(InstCreateThread) { RunThread(newThread, dt); BlockCurrentScriptThread = false; } + +VmInstruction(InstCreateThreadNew) { + StartInstruction; + PopUint8(type); + PopExpression(groupId); + PopExpression(scriptBufferId); + PopFarLabel(labelAdr, scriptBufferId); + + [[maybe_unused]] + char* threadName = nullptr; + if (type & 0x80) { + threadName = reinterpret_cast(thread->GetIp()); + do { + thread->IpOffset++; + } while ((*thread->GetIp()) != '\0'); + thread->IpOffset++; + } + + Sc3VmThread* newThread = CreateThread(groupId); + newThread->GroupId = groupId; + newThread->ScriptBufferId = scriptBufferId; + newThread->IpOffset = labelAdr; + thread->ScriptParam = newThread->Id; + newThread->ScriptParam = thread->Id; + RunThread(newThread, dt); + BlockCurrentScriptThread = false; + + ImpLogSlow(LogLevel::Trace, LogChannel::VM, + "CreateThread(threadId: {:d}, name: {})\n", newThread->Id, + threadName ? threadName : "unname"); +} VmInstruction(InstKillThread) { StartInstruction; PopExpression(threadId); @@ -79,6 +110,18 @@ VmInstruction(InstScriptLoad) { } LoadScript(bufferId, scriptId); } +VmInstruction(InstScriptLoadNew) { + StartInstruction; + PopUint8(type); + PopExpression(bufferId); + PopExpression(scriptId); + if (Profile::Vm::UseMsbStrings) { + LoadMsb(bufferId, scriptId); + if (!Profile::Vm::UseSeparateMsbArchive) scriptId += 1; + } + if (type == 1) return; + LoadScript(bufferId, scriptId); +} VmInstruction(InstWait) { StartInstruction; PopExpression(waitCycles); @@ -116,11 +159,47 @@ VmInstruction(InstSetFlag) { PopExpression(flagId); SetFlag(flagId, 1); } +VmInstruction(InstSetFlagNew) { + StartInstruction; + PopUint8(type); + PopExpression(flagId); + if (flagId < 0) return; + if (type == 0) { + SetFlag(flagId, 1); + } else { + PopExpression(range); + if (type == 1) { + range = flagId + range; + } + // type 1: [flagId, flagId+range), type 2: [flagId, range) + for (uint32_t i = flagId; i < static_cast(range); i++) { + SetFlag(i, 1); + } + } +} VmInstruction(InstResetFlag) { StartInstruction; PopExpression(flagId); SetFlag(flagId, 0); } +VmInstruction(InstResetFlagNew) { + StartInstruction; + PopUint8(type); + PopExpression(flagId); + if (flagId < 0) return; + if (type == 0) { + SetFlag(flagId, 0); + } else { + PopExpression(range); + if (type == 0) { + range = flagId + range; + } + // type 1: [flagId, flagId+range), type 2: [flagId, range) + for (uint32_t i = flagId; i < static_cast(range); i++) { + SetFlag(i, 0); + } + } +} VmInstruction(InstCopyFlag) { StartInstruction; PopExpression(flagIdSrc); @@ -267,7 +346,8 @@ VmInstruction(InstSave) { StartInstruction; PopUint8(type); switch (type) { // TODO: Types 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 19, 40, - // 41, 45, 46, 47, 50, 51, 52, 53, 71, 88, 89, 150, 151 + // 41, 45, 46, 47, 50, 51, 52, 53, 71, 88, 89, 150, 151, + // 210, 211 case 0: { SaveSystem::SaveSystemData(); break; @@ -313,10 +393,28 @@ VmInstruction(InstSave) { PopExpression(unused3); PopExpression(unused4); } + + if (Profile::Vm::GameInstructionSet != InstructionSet::LCCSwitch) { + SetFlag(SF_SAVE_UNK1, 1); + } ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, "STUB instruction Save(type: {:d})\n", type); break; } + case 45: { + SetFlag(SF_SAVE_UNK2, 1); + break; + } + case 46: { + SetFlag(SF_SAVE_UNK3, 1); + break; + } + case 1: + case 41: + case 47: { + ScrWork[SW_SAVEERRORCODE] = 0; + break; + } case 60: { SetFlag(SF_SAVEICON, true); SaveSystem::WriteSaveFile(); @@ -360,10 +458,23 @@ VmInstruction(InstSave) { } break; } + case 200: { + SetFlag(SF_SAVEICON, true); + SaveSystem::WriteQuickSaveFile(); + break; + } + case 201: { + if (SaveSystem::GetLoadStatus() == LoadStatus::Loading) { + ResetInstruction; + BlockThread; + } else { + SetFlag(SF_SAVEICON, false); + } + } + case 86: // NOOP by design case 87: // NOOP by design break; - case 1: case 2: case 3: case 6: @@ -374,10 +485,6 @@ VmInstruction(InstSave) { case 11: case 12: case 19: - case 41: - case 45: - case 46: - case 47: case 50: case 51: case 52: @@ -386,6 +493,9 @@ VmInstruction(InstSave) { case 89: case 150: case 151: + case 210: + case 211: + break; default: { ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, @@ -525,6 +635,28 @@ VmInstruction(InstVoiceTableLoadMaybe) { break; } } +VmInstruction(InstVoiceTableLoadNew) { + StartInstruction; + PopUint8(type); + PopExpression(fileId); + if (type == 0) { + switch (VoiceTableData.Status) { + case LoadStatus::Unloaded: + VoiceTableData.LoadAsync(fileId); + ResetInstruction; + BlockThread; + break; + case LoadStatus::Loading: + ResetInstruction; + BlockThread; + break; + case LoadStatus::Loaded: + break; + } + } else { + // TODO: "voice len table" + } +} VmInstruction(InstSetPadCustom) { StartInstruction; Interface::UpdatePADcustomType(Profile::ConfigSystem::ControllerType); @@ -567,11 +699,13 @@ VmInstruction(InstSystemMes) { StartInstruction; PopUint8(mode); uint32_t sysMesId = thread->Id; + const bool useMsb = mode & 0x80; switch (Profile::Vm::GameInstructionSet) { default: break; case InstructionSet::Dash: case InstructionSet::CC: + case InstructionSet::LCCSwitch: PopUint8(id); sysMesId = id; break; @@ -587,7 +721,19 @@ VmInstruction(InstSystemMes) { return box; }; - switch (mode) { + uint32_t type = mode; + if (useMsb) { + type -= 0x80; + } + if (type & 0x40) { + type -= 0x40; + } + // bool flag = type > 0xf; + if (type > 0x10) { + type -= 0x10; + } + + switch (type) { case 0: // SystemMesInit0 case 1: // SystemMesInit1 UI::SysMesBox::Push(sysMesId); @@ -598,22 +744,42 @@ VmInstruction(InstSystemMes) { if (!box) break; ScrWork[SW_SYSMESANIMCTF] = 2 * box->MessageCount + 33; } break; - case 3: { // SystemMesSetMes - PopUint16(sysMesStrNum); - UI::SysMesBox* box = activeBox(); - if (!box) break; - const uint32_t message = - ScriptGetStrAddress(thread->ScriptBufferId, sysMesStrNum); - box->AddMessage( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message}); - } break; + case 3: // SystemMesSetMes case 4: { // SystemMesSetSel - PopUint16(sysSelStrNum); + uint32_t message; + + if (useMsb) { + PopUint8(unk02); + if (unk02 == 1) { + PopExpression(unk03); + } + [[maybe_unused]] + int bufId; + if ((type >> 6 & 1) == 0) { + // TODO get buf id from thread field + bufId = 0; + } else { + bufId = ExpressionEval(thread); + } + + PopExpression(stringNum); + message = MsbGetStrAddress(thread->ScriptBufferId, stringNum); + } else { + PopUint16(sysSelStrNum); + message = ScriptGetStrAddress(thread->ScriptBufferId, sysSelStrNum); + } + UI::SysMesBox* box = activeBox(); if (!box) break; - auto message = ScriptGetStrAddress(thread->ScriptBufferId, sysSelStrNum); - box->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message}); + if (type == 3) { + box->AddMessage({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = message}); + } else { + box->AddChoice({.Buffers = {}, + .BufferId = thread->ScriptBufferId, + .IpOffset = message}); + } } break; case 5: { // SystemMesMain UI::SysMesBox* box = activeBox(); @@ -662,20 +828,6 @@ VmInstruction(InstSystemMes) { ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, "STUB instruction SystemMes(mode: {:d})\n", mode); break; - case 0x83: { - PopMsbString(message); - UI::SysMesBox* box = activeBox(); - if (!box) break; - box->AddMessage( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message}); - } break; - case 0x84: { // SystemMesSetSel - PopMsbString(message); - UI::SysMesBox* box = activeBox(); - if (!box) break; - box->AddChoice( - {.ScriptBufferId = thread->ScriptBufferId, .IpOffset = message}); - } break; default: ImpLog(LogLevel::Warning, LogChannel::VMStub, "Unknown mode for instruction SystemMes(mode: {:d})\n", mode); @@ -861,6 +1013,9 @@ VmInstruction(InstMSinit) { } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC) { memset(&FlagWork, 0, 1000); memset(&ScrWork, 0, 32000); + } else if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { + memset(&FlagWork, 0, 800); + memset(&ScrWork, 0, 32000); } ScrWork[SW_SYSMESALPHA] = 255; @@ -928,9 +1083,15 @@ VmInstruction(InstMSinit) { if (initType == 2) { UI::BacklogMenuPtr->Clear(); - memset(&FlagWork, 0, 100); - memset(&FlagWork[150], 0, 75); - memset(&FlagWork[300], 0, 100); + if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { + memset(&FlagWork, 0, 200); + memset(&FlagWork[250], 0, 75); + memset(&FlagWork[400], 0, 100); + } else { + memset(&FlagWork, 0, 100); + memset(&FlagWork[150], 0, 75); + memset(&FlagWork[300], 0, 100); + } if (Profile::Vm::GameInstructionSet == InstructionSet::MO6TW || Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) { @@ -994,6 +1155,13 @@ VmInstruction(InstMSinit) { ScrWork[SW_INTROVOICE] = 999; } + if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { + for (int i = 0; i < 8; i++) { + ScrWork[SW_PIC_REQ_ARCHIVENO1 + 2 * i] = 0xFFFF; + ScrWork[SW_PIC_REQ_FILENO1 + 2 * i] = 0xFFFF; + } + } + if (initType == 0 || initType == 1) { for (int i = 0; i < std::ssize(DialoguePages); i++) { DialoguePages[i].Clear(); @@ -1151,12 +1319,15 @@ VmInstruction(InstAutoSave) { ScrWork[SW_AUTOSAVERESTART] = 0; } break; - + case 40: { + if (GetFlag(SF_SAVE_UNK1)) break; + SetFlag(SF_SAVE_UNK1, 1); + ScrWork[SW_AUTOSAVERESTART] = 0; + } break; case 0xff: { SetFlag(SF_SAVECAPTURE, 1); BlockThread; } break; - case 2: default: ImpLog(LogLevel::Warning, LogChannel::VM, @@ -1253,6 +1424,13 @@ VmInstruction(InstLoadFontWidths) { fontId, archiveId, fileId); } +VmInstruction(InstCPUBoostMode) { + StartInstruction; + PopUint8(arg1); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction CPUBoostMode(arg1: {:d})\n", arg1); +} + } // namespace Vm } // namespace Impacto \ No newline at end of file diff --git a/src/vm/inst_system.h b/src/vm/inst_system.h index 021d647ab..dd93cf42c 100644 --- a/src/vm/inst_system.h +++ b/src/vm/inst_system.h @@ -10,15 +10,19 @@ VmInstruction(InstDummy); VmInstruction(InstEnd); VmInstruction(InstCreateThread); +VmInstruction(InstCreateThreadNew); VmInstruction(InstKillThread); VmInstruction(InstReset); VmInstruction(InstScriptLoad); +VmInstruction(InstScriptLoadNew); VmInstruction(InstWait); VmInstruction(InstHalt); VmInstruction(InstGetLabelAdr); VmInstruction(InstFlagOnWait); VmInstruction(InstSetFlag); +VmInstruction(InstSetFlagNew); VmInstruction(InstResetFlag); +VmInstruction(InstResetFlagNew); VmInstruction(InstCopyFlag); VmInstruction(InstKeyWait); VmInstruction(InstKeyWaitTimer); @@ -34,6 +38,7 @@ VmInstruction(InstSave); VmInstruction(InstSaveOld); VmInstruction(InstSaveIconLoad); VmInstruction(InstVoiceTableLoadMaybe); +VmInstruction(InstVoiceTableLoadNew); VmInstruction(InstSetPadCustom); VmInstruction(InstMwait); VmInstruction(InstTerminate); @@ -58,6 +63,7 @@ VmInstruction(InstDebugData); VmInstruction(InstAutoSave); VmInstruction(InstAutoSaveOld); VmInstruction(InstLoadFontWidths); +VmInstruction(InstCPUBoostMode); } // namespace Vm diff --git a/src/vm/opcodetables_lccswitch.h b/src/vm/opcodetables_lccswitch.h new file mode 100644 index 000000000..3cc509e2f --- /dev/null +++ b/src/vm/opcodetables_lccswitch.h @@ -0,0 +1,1060 @@ +#include "inst_system.h" +#include "inst_controlflow.h" +#include "inst_graphics2d.h" +#include "inst_graphics3d.h" +#include "inst_dialogue.h" +#include "inst_sound.h" +#include "inst_movie.h" +#include "inst_gamespecific.h" +#include "inst_misc.h" + +// clang-format off + + +namespace Impacto { +namespace Vm { + +InstructionProc inline constexpr OpcodeTableSystem_LCCSwitch[256] = { + InstEnd, // 00 00 + InstCreateThreadNew, // 00 01 + InstKillThread, // 00 02 + InstReset, // 00 03 + InstScriptLoadNew, // 00 04 + InstWait, // 00 05 + InstHalt, // 00 06 + InstJump, // 00 07 + InstJumpTable, // 00 08 + InstGetLabelAdr, // 00 09 + InstIf, // 00 0A + InstCall, // 00 0B + InstJumpFar, // 00 0C + InstCallFar, // 00 0D + InstReturn, // 00 0E + InstLoop, // 00 0F + InstFlagOnJump, // 00 10 + InstFlagOnWait, // 00 11 + InstSetFlagNew, // 00 12 + InstResetFlagNew, // 00 13 + InstCopyFlag, // 00 14 + InstKeyOnJump, // 00 15 + InstKeyWait, // 00 16 + InstKeyWaitTimer, // 00 17 + InstMemberWrite, // 00 18 + InstThreadControl, // 00 19 + InstGetSelfPointer, // 00 1A + InstLoadJump, // 00 1B + InstVsync, // 00 1C + InstTest, // 00 1D + InstThreadControlStore, // 00 1E + InstSwitch, // 00 1F + InstCase, // 00 20 + InstBGMplay, // 00 21 + InstBGMstop, // 00 22 + InstSEplay, // 00 23 + InstSEstop, // 00 24 + InstPadAct, // 00 25 + InstSSEplay, // 00 26 + InstSSEstop, // 00 27 + InstCopyThreadWork, // 00 28 + InstUPLmenuUI, // 00 29 + InstSave, // 00 2A + InstSaveIconLoad, // 00 2B + InstBGMflag, // 00 2C + InstUPLxTitle, // 00 2D + InstPresence, // 00 2E + InstSetAchievement, // 00 2F + InstSetPlayer, // 00 30 + InstVoiceTableLoadNew, // 00 31 + InstSetPadCustom, // 00 32 + InstMwait, // 00 33 + InstTerminate, // 00 34 + InstSignIn, // 00 35 + InstAchievementIcon, // 00 36 + InstVoicePlay, // 00 37 + InstVoiceStopNew, // 00 38 + InstVoicePlayWait, // 00 39 + InstBGMduelPlay, // 00 3A + InstSNDpause, // 00 3B + InstSEplayWait, // 00 3C + InstDebugPrint, // 00 3D + InstResetSoundAll, // 00 3E + InstSNDloadStop, // 00 3F + InstBGMstopWait, // 00 40 + InstUnk0041, // 00 41 + InstSetX360SysMesPos, // 00 42 + InstSystemMes, // 00 43 + InstSystemMenu, // 00 44 + InstGetNowTime, // 00 45 + InstGetSystemStatus, // 00 46 + InstReboot, // 00 47 + InstReloadScript, // 00 48 + InstReloadScriptMenu, // 00 49 + InstDebugEditer, // 00 4A + InstSysSeload, // 00 4B + InstDummy, // 00 4C + InstSysVoicePlay, // 00 4D + InstPadActEx, // 00 4E + InstDebugSetup, // 00 4F + InstPressStartNew, // 00 50 + InstGlobalSystemMessage, // 00 51 + InstUnk0052, // 00 52 + InstControlOnJump, // 00 53 + InstKeyboardOnJump, // 00 54 + InstFlagOnJumpFar, // 00 55 + InstGetControl, // 00 56 + InstDummy, // 00 57 + InstDummy, // 00 58 + InstDummy, // 00 59 + InstDummy, // 00 5A + InstSaveIconState, // 00 5B + InstFlagOffReturn, // 00 5C + InstDummy, // 00 5D + InstCPUBoostMode, // 00 5E + InstPackFileAddBind, // 00 5F + InstDummy, // 00 60 + InstDummy, // 00 61 + InstDummy, // 00 62 + InstDummy, // 00 63 + InstDummy, // 00 64 + InstDummy, // 00 65 + InstDummy, // 00 66 + InstDummy, // 00 67 + InstDummy, // 00 68 + InstDummy, // 00 69 + InstDummy, // 00 6A + InstDummy, // 00 6B + InstDummy, // 00 6C + InstDummy, // 00 6D + InstDummy, // 00 6E + InstDummy, // 00 6F + InstDummy, // 00 70 + InstDummy, // 00 71 + InstDummy, // 00 72 + InstDummy, // 00 73 + InstDummy, // 00 74 + InstDummy, // 00 75 + InstDummy, // 00 76 + InstDummy, // 00 77 + InstDummy, // 00 78 + InstDummy, // 00 79 + InstDummy, // 00 7A + InstDummy, // 00 7B + InstDummy, // 00 7C + InstDummy, // 00 7D + InstDummy, // 00 7E + InstDummy, // 00 7F + InstDummy, // 00 80 + InstDummy, // 00 81 + InstDummy, // 00 82 + InstDummy, // 00 83 + InstDummy, // 00 84 + InstDummy, // 00 85 + InstDummy, // 00 86 + InstDummy, // 00 87 + InstDummy, // 00 88 + InstDummy, // 00 89 + InstDummy, // 00 8A + InstDummy, // 00 8B + InstDummy, // 00 8C + InstDummy, // 00 8D + InstDummy, // 00 8E + InstDummy, // 00 8F + InstDummy, // 00 90 + InstDummy, // 00 91 + InstDummy, // 00 92 + InstDummy, // 00 93 + InstDummy, // 00 94 + InstDummy, // 00 95 + InstDummy, // 00 96 + InstDummy, // 00 97 + InstDummy, // 00 98 + InstDummy, // 00 99 + InstDummy, // 00 9A + InstDummy, // 00 9B + InstDummy, // 00 9C + InstDummy, // 00 9D + InstDummy, // 00 9E + InstDummy, // 00 9F + InstDummy, // 00 A0 + InstDummy, // 00 A1 + InstDummy, // 00 A2 + InstDummy, // 00 A3 + InstDummy, // 00 A4 + InstDummy, // 00 A5 + InstDummy, // 00 A6 + InstDummy, // 00 A7 + InstDummy, // 00 A8 + InstDummy, // 00 A9 + InstDummy, // 00 AA + InstDummy, // 00 AB + InstDummy, // 00 AC + InstDummy, // 00 AD + InstDummy, // 00 AE + InstDummy, // 00 AF + InstDummy, // 00 B0 + InstDummy, // 00 B1 + InstDummy, // 00 B2 + InstDummy, // 00 B3 + InstDummy, // 00 B4 + InstDummy, // 00 B5 + InstDummy, // 00 B6 + InstDummy, // 00 B7 + InstDummy, // 00 B8 + InstDummy, // 00 B9 + InstDummy, // 00 BA + InstDummy, // 00 BB + InstDummy, // 00 BC + InstDummy, // 00 BD + InstDummy, // 00 BE + InstDummy, // 00 BF + InstDummy, // 00 C0 + InstDummy, // 00 C1 + InstDummy, // 00 C2 + InstDummy, // 00 C3 + InstDummy, // 00 C4 + InstDummy, // 00 C5 + InstDummy, // 00 C6 + InstDummy, // 00 C7 + InstDummy, // 00 C8 + InstDummy, // 00 C9 + InstDummy, // 00 CA + InstDummy, // 00 CB + InstDummy, // 00 CC + InstDummy, // 00 CD + InstDummy, // 00 CE + InstDummy, // 00 CF + InstDummy, // 00 D0 + InstDummy, // 00 D1 + InstDummy, // 00 D2 + InstDummy, // 00 D3 + InstDummy, // 00 D4 + InstDummy, // 00 D5 + InstDummy, // 00 D6 + InstDummy, // 00 D7 + InstDummy, // 00 D8 + InstDummy, // 00 D9 + InstDummy, // 00 DA + InstDummy, // 00 DB + InstDummy, // 00 DC + InstDummy, // 00 DD + InstDummy, // 00 DE + InstDummy, // 00 DF + InstDummy, // 00 E0 + InstDummy, // 00 E1 + InstDummy, // 00 E2 + InstDummy, // 00 E3 + InstDummy, // 00 E4 + InstDummy, // 00 E5 + InstDummy, // 00 E6 + InstDummy, // 00 E7 + InstDummy, // 00 E8 + InstDummy, // 00 E9 + InstDummy, // 00 EA + InstDummy, // 00 EB + InstDummy, // 00 EC + InstDummy, // 00 ED + InstDummy, // 00 EE + InstDummy, // 00 EF + InstDummy, // 00 F0 + InstDummy, // 00 F1 + InstDummy, // 00 F2 + InstDummy, // 00 F3 + InstDummy, // 00 F4 + InstDummy, // 00 F5 + InstDummy, // 00 F6 + InstDummy, // 00 F7 + InstDummy, // 00 F8 + InstDummy, // 00 F9 + InstDummy, // 00 FA + InstDummy, // 00 FB + InstDummy, // 00 FC + InstDummy, // 00 FD + InstDummy, // 00 FE + InstDummy // 00 FF +}; + +InstructionProc inline constexpr OpcodeTableGraph_LCCSwitch[256] = { + InstCreateSurf, // 01 00 + InstReleaseSurfNew, // 01 01 + InstLoadPic, // 01 02 + InstDummy, // 01 03 + InstSurfFill, // 01 04 + InstCalc, // 01 05 + InstMesViewFlag, // 01 06 + InstSetMesWinPri, // 01 07 + InstMesSync, // 01 08 + InstMesSetID, // 01 09 + InstMesCls, // 01 0A + InstMesVoiceWait, // 01 0B + InstMes, // 01 0C + InstMesMain, // 01 0D + InstSetMesModeFormat, // 01 0E + InstSetNGmoji, // 01 0F + InstMesRev, // 01 10 + InstMessWindow, // 01 11 + InstSel, // 01 12 + InstSelect, // 01 13 + InstSysSel, // 01 14 + InstSysSelect, // 01 15 + InstDummy, // 01 16 + InstDummy, // 01 17 + InstDummy, // 01 18 + InstDummy, // 01 19 + InstDummy, // 01 1A + InstDummy, // 01 1B + InstDummy, // 01 1C + InstDummy, // 01 1D + InstDummy, // 01 1E + InstLoadFontWidths, // 01 1F + InstSCcapture, // 01 20 + InstSetTextTable, // 01 21 + InstPlayMovie, // 01 22 + InstMovieMain, // 01 23 + InstLoadMovie, // 01 24 + InstSetRevMes, // 01 25 + InstPlayMovieMemory, // 01 26 + InstMovie, // 01 27 + InstMovieMain, // 01 28 not a MovieMain + InstLoadMovie, // 01 29 + InstPlayMovieMemory, // 01 2A + InstSFDpause, // 01 2B + InstPlayMovie, // 01 2C + InstUnk012D, // 01 2D + InstDummy, // 01 2E + InstDummy, // 01 2F + InstLoadFontMeta, // 01 30 + InstDummy, // 01 31 + InstDummy, // 01 32 + InstDummy, // 01 33 + InstDummy, // 01 34 + InstDummy, // 01 35 + InstDummy, // 01 36 + InstDummy, // 01 37 + InstDummy, // 01 38 + InstDummy, // 01 39 + InstDummy, // 01 3A + InstDummy, // 01 3B + InstDummy, // 01 3C + InstDummy, // 01 3D + InstDummy, // 01 3E + InstDummy, // 01 3F + InstDummy, // 01 40 + InstDummy, // 01 41 + InstDummy, // 01 42 + InstDummy, // 01 43 + InstDummy, // 01 44 + InstDummy, // 01 45 + InstDummy, // 01 46 + InstDummy, // 01 47 + InstDummy, // 01 48 + InstDummy, // 01 49 + InstDummy, // 01 4A + InstDummy, // 01 4B + InstDummy, // 01 4C + InstDummy, // 01 4D + InstDummy, // 01 4E + InstDummy, // 01 4F + InstDummy, // 01 50 + InstDummy, // 01 51 + InstDummy, // 01 52 + InstDummy, // 01 53 + InstDummy, // 01 54 + InstDummy, // 01 55 + InstDummy, // 01 56 + InstDummy, // 01 57 + InstDummy, // 01 58 + InstDummy, // 01 59 + InstDummy, // 01 5A + InstDummy, // 01 5B + InstDummy, // 01 5C + InstDummy, // 01 5D + InstDummy, // 01 5E + InstDummy, // 01 5F + InstDummy, // 01 60 + InstDummy, // 01 61 + InstDummy, // 01 62 + InstDummy, // 01 63 + InstDummy, // 01 64 + InstDummy, // 01 65 + InstDummy, // 01 66 + InstDummy, // 01 67 + InstDummy, // 01 68 + InstDummy, // 01 69 + InstDummy, // 01 6A + InstDummy, // 01 6B + InstDummy, // 01 6C + InstDummy, // 01 6D + InstDummy, // 01 6E + InstDummy, // 01 6F + InstDummy, // 01 70 + InstDummy, // 01 71 + InstDummy, // 01 72 + InstDummy, // 01 73 + InstDummy, // 01 74 + InstDummy, // 01 75 + InstDummy, // 01 76 + InstDummy, // 01 77 + InstDummy, // 01 78 + InstDummy, // 01 79 + InstDummy, // 01 7A + InstDummy, // 01 7B + InstDummy, // 01 7C + InstDummy, // 01 7D + InstDummy, // 01 7E + InstDummy, // 01 7F + InstDummy, // 01 80 + InstDummy, // 01 81 + InstDummy, // 01 82 + InstDummy, // 01 83 + InstDummy, // 01 84 + InstDummy, // 01 85 + InstDummy, // 01 86 + InstDummy, // 01 87 + InstDummy, // 01 88 + InstDummy, // 01 89 + InstDummy, // 01 8A + InstDummy, // 01 8B + InstDummy, // 01 8C + InstDummy, // 01 8D + InstDummy, // 01 8E + InstDummy, // 01 8F + InstDummy, // 01 90 + InstDummy, // 01 91 + InstDummy, // 01 92 + InstDummy, // 01 93 + InstDummy, // 01 94 + InstDummy, // 01 95 + InstDummy, // 01 96 + InstDummy, // 01 97 + InstDummy, // 01 98 + InstDummy, // 01 99 + InstDummy, // 01 9A + InstDummy, // 01 9B + InstDummy, // 01 9C + InstDummy, // 01 9D + InstDummy, // 01 9E + InstDummy, // 01 9F + InstDummy, // 01 A0 + InstDummy, // 01 A1 + InstDummy, // 01 A2 + InstDummy, // 01 A3 + InstDummy, // 01 A4 + InstDummy, // 01 A5 + InstDummy, // 01 A6 + InstDummy, // 01 A7 + InstDummy, // 01 A8 + InstDummy, // 01 A9 + InstDummy, // 01 AA + InstDummy, // 01 AB + InstDummy, // 01 AC + InstDummy, // 01 AD + InstDummy, // 01 AE + InstDummy, // 01 AF + InstDummy, // 01 B0 + InstDummy, // 01 B1 + InstDummy, // 01 B2 + InstDummy, // 01 B3 + InstDummy, // 01 B4 + InstDummy, // 01 B5 + InstDummy, // 01 B6 + InstDummy, // 01 B7 + InstDummy, // 01 B8 + InstDummy, // 01 B9 + InstDummy, // 01 BA + InstDummy, // 01 BB + InstDummy, // 01 BC + InstDummy, // 01 BD + InstDummy, // 01 BE + InstDummy, // 01 BF + InstDummy, // 01 C0 + InstDummy, // 01 C1 + InstDummy, // 01 C2 + InstDummy, // 01 C3 + InstDummy, // 01 C4 + InstDummy, // 01 C5 + InstDummy, // 01 C6 + InstDummy, // 01 C7 + InstDummy, // 01 C8 + InstDummy, // 01 C9 + InstDummy, // 01 CA + InstDummy, // 01 CB + InstDummy, // 01 CC + InstDummy, // 01 CD + InstDummy, // 01 CE + InstDummy, // 01 CF + InstDummy, // 01 D0 + InstDummy, // 01 D1 + InstDummy, // 01 D2 + InstDummy, // 01 D3 + InstDummy, // 01 D4 + InstDummy, // 01 D5 + InstDummy, // 01 D6 + InstDummy, // 01 D7 + InstDummy, // 01 D8 + InstDummy, // 01 D9 + InstDummy, // 01 DA + InstDummy, // 01 DB + InstDummy, // 01 DC + InstDummy, // 01 DD + InstDummy, // 01 DE + InstDummy, // 01 DF + InstDummy, // 01 E0 + InstDummy, // 01 E1 + InstDummy, // 01 E2 + InstDummy, // 01 E3 + InstDummy, // 01 E4 + InstDummy, // 01 E5 + InstDummy, // 01 E6 + InstDummy, // 01 E7 + InstDummy, // 01 E8 + InstDummy, // 01 E9 + InstDummy, // 01 EA + InstDummy, // 01 EB + InstDummy, // 01 EC + InstDummy, // 01 ED + InstDummy, // 01 EE + InstDummy, // 01 EF + InstDummy, // 01 F0 + InstDummy, // 01 F1 + InstDummy, // 01 F2 + InstDummy, // 01 F3 + InstDummy, // 01 F4 + InstDummy, // 01 F5 + InstDummy, // 01 F6 + InstDummy, // 01 F7 + InstDummy, // 01 F8 + InstDummy, // 01 F9 + InstDummy, // 01 FA + InstDummy, // 01 FB + InstDummy, // 01 FC + InstDummy, // 01 FD + InstDummy, // 01 FE + InstDummy // 01 FF +}; + +InstructionProc inline constexpr OpcodeTableUser1_LCCSwitch[256] = { + + InstMSinit, // 10 00 + InstBGloadNew, // 10 01 + InstBGswap, // 10 02 + InstBGsetColor, // 10 03 + InstBGsetLink, // 10 04 + InstCHAload, // 10 05 + InstCHAswap, // 10 06 + InstBGcopy, // 10 07 + InstCHAcopy, // 10 08 + InstSaveSlot, // 10 09 + InstSystemMain, // 10 0A + InstCharaLayerLoad, // 10 0B + InstGameInfoInit, // 10 0C + InstCHAmove, // 10 0D // dummy for switch cclcc + InstBGloadEx, // 10 0E + InstDummy, // 10 0F + InstBGrelease, // 10 10 + InstCHArelease, // 10 11 + InstClearFlagChk, // 10 12 + InstOption, // 10 13 + InstSystemDataReset, // 10 14 + InstDebugData, // 10 15 + InstGetCharaPause, // 10 16 + InstBGfadeExpInit, // 10 17 + InstDummy, // 10 18 + InstDummy, // 10 19 + InstHelp, // 10 1A + InstDummy, // 10 1B + InstAchievementMenu, // 10 1C + InstSoundMenu, // 10 1D + InstAllClear, // 10 1E + InstAlbum, // 10 1F + InstMovieMode, // 10 20 + InstClistInit, // 10 21 + InstAutoSave, // 10 22 + InstSaveMenu, // 10 23 + InstLoadData, // 10 24 + InstDummy, // 10 25 + InstDummy, // 10 26 + InstSetDic, // 10 27 + InstSetPlayMode, // 10 28 + InstSetEVflag, // 10 29 + InstSetCutin, // 10 2A + InstDummy, // 10 2B + InstDummy, // 10 2C + InstAchChkTitle, // 10 2D + InstSetSceneViewFlag, // 10 2E + InstChkClearFlag, // 10 2F + InstBGeffectWave, // 10 30 + InstGeotag, // 10 31 + InstNameID, // 10 32 + InstTips, // 10 33 + InstTitleMenuNew, // 10 34 + InstDummy, // 10 35 + InstBGeffect, // 10 36 + InstDummy, // 10 37 + InstMapSystem, // 10 38 + InstMtrg, // 10 39 + InstYesNoTriggerCCLCC, // 10 3A + InstDummy, // 10 3B + InstDummy, // 10 3C + InstDummy, // 10 3D + InstDummy, // 10 3E + InstLaunchApp, // 10 3F + InstScreenChange, // 10 40 + InstExitGame, // 10 41 + InstDummy, // 10 42 + InstDummy, // 10 43 + InstDummy, // 10 44 + InstDummy, // 10 45 + InstDummy, // 10 46 + InstDummy, // 10 47 + InstDummy, // 10 48 + InstDummy, // 10 49 + InstDummy, // 10 4A + InstDummy, // 10 4B + InstDummy, // 10 4C + InstDummy, // 10 4D + InstDummy, // 10 4E + InstDummy, // 10 4F + InstDummy, // 10 50 + InstDummy, // 10 51 + InstDummy, // 10 52 + InstDummy, // 10 53 + InstDummy, // 10 54 + InstDummy, // 10 55 + InstDummy, // 10 56 + InstDummy, // 10 57 + InstDummy, // 10 58 + InstDummy, // 10 59 + InstDummy, // 10 5A + InstDummy, // 10 5B + InstDummy, // 10 5C + InstDummy, // 10 5D + InstDummy, // 10 5E + InstDummy, // 10 5F + InstDummy, // 10 60 + InstDummy, // 10 61 + InstDummy, // 10 62 + InstDummy, // 10 63 + InstDummy, // 10 64 + InstDummy, // 10 65 + InstDummy, // 10 66 + InstDummy, // 10 67 + InstDummy, // 10 68 + InstDummy, // 10 69 + InstDummy, // 10 6A + InstDummy, // 10 6B + InstDummy, // 10 6C + InstDummy, // 10 6D + InstDummy, // 10 6E + InstDummy, // 10 6F + InstDummy, // 10 70 + InstDummy, // 10 71 + InstDummy, // 10 72 + InstDummy, // 10 73 + InstDummy, // 10 74 + InstDummy, // 10 75 + InstDummy, // 10 76 + InstDummy, // 10 77 + InstDummy, // 10 78 + InstDummy, // 10 79 + InstDummy, // 10 7A + InstDummy, // 10 7B + InstDummy, // 10 7C + InstDummy, // 10 7D + InstDummy, // 10 7E + InstDummy, // 10 7F + InstDummy, // 10 80 + InstDummy, // 10 81 + InstDummy, // 10 82 + InstDummy, // 10 83 + InstDummy, // 10 84 + InstDummy, // 10 85 + InstDummy, // 10 86 + InstDummy, // 10 87 + InstDummy, // 10 88 + InstDummy, // 10 89 + InstDummy, // 10 8A + InstDummy, // 10 8B + InstDummy, // 10 8C + InstDummy, // 10 8D + InstDummy, // 10 8E + InstDummy, // 10 8F + InstDummy, // 10 90 + InstDummy, // 10 91 + InstDummy, // 10 92 + InstDummy, // 10 93 + InstDummy, // 10 94 + InstDummy, // 10 95 + InstDummy, // 10 96 + InstDummy, // 10 97 + InstDummy, // 10 98 + InstDummy, // 10 99 + InstDummy, // 10 9A + InstDummy, // 10 9B + InstDummy, // 10 9C + InstDummy, // 10 9D + InstDummy, // 10 9E + InstDummy, // 10 9F + InstDummy, // 10 A0 + InstDummy, // 10 A1 + InstDummy, // 10 A2 + InstDummy, // 10 A3 + InstDummy, // 10 A4 + InstDummy, // 10 A5 + InstDummy, // 10 A6 + InstDummy, // 10 A7 + InstDummy, // 10 A8 + InstDummy, // 10 A9 + InstDummy, // 10 AA + InstDummy, // 10 AB + InstDummy, // 10 AC + InstDummy, // 10 AD + InstDummy, // 10 AE + InstDummy, // 10 AF + InstDummy, // 10 B0 + InstDummy, // 10 B1 + InstDummy, // 10 B2 + InstDummy, // 10 B3 + InstDummy, // 10 B4 + InstDummy, // 10 B5 + InstDummy, // 10 B6 + InstDummy, // 10 B7 + InstDummy, // 10 B8 + InstDummy, // 10 B9 + InstDummy, // 10 BA + InstDummy, // 10 BB + InstDummy, // 10 BC + InstDummy, // 10 BD + InstDummy, // 10 BE + InstDummy, // 10 BF + InstDummy, // 10 C0 + InstDummy, // 10 C1 + InstDummy, // 10 C2 + InstDummy, // 10 C3 + InstDummy, // 10 C4 + InstDummy, // 10 C5 + InstDummy, // 10 C6 + InstDummy, // 10 C7 + InstDummy, // 10 C8 + InstDummy, // 10 C9 + InstDummy, // 10 CA + InstDummy, // 10 CB + InstDummy, // 10 CC + InstDummy, // 10 CD + InstDummy, // 10 CE + InstDummy, // 10 CF + InstDummy, // 10 D0 + InstDummy, // 10 D1 + InstDummy, // 10 D2 + InstDummy, // 10 D3 + InstDummy, // 10 D4 + InstDummy, // 10 D5 + InstDummy, // 10 D6 + InstDummy, // 10 D7 + InstDummy, // 10 D8 + InstDummy, // 10 D9 + InstDummy, // 10 DA + InstDummy, // 10 DB + InstDummy, // 10 DC + InstDummy, // 10 DD + InstDummy, // 10 DE + InstDummy, // 10 DF + InstDummy, // 10 E0 + InstDummy, // 10 E1 + InstDummy, // 10 E2 + InstDummy, // 10 E3 + InstDummy, // 10 E4 + InstDummy, // 10 E5 + InstDummy, // 10 E6 + InstDummy, // 10 E7 + InstDummy, // 10 E8 + InstDummy, // 10 E9 + InstDummy, // 10 EA + InstDummy, // 10 EB + InstDummy, // 10 EC + InstDummy, // 10 ED + InstDummy, // 10 EE + InstDummy, // 10 EF + InstDummy, // 10 F0 + InstDummy, // 10 F1 + InstDummy, // 10 F2 + InstDummy, // 10 F3 + InstDummy, // 10 F4 + InstDummy, // 10 F5 + InstDummy, // 10 F6 + InstDummy, // 10 F7 + InstDummy, // 10 F8 + InstDummy, // 10 F9 + InstDummy, // 10 FA + InstDummy, // 10 FB + InstDummy, // 10 FC + InstDummy, // 10 FD + InstDummy, // 10 FE + InstDummy // 10 FF +}; + + +InstructionProc inline constexpr OpcodeTableTitleOnly_LCCSwitch[256] = { + InstDummy, // 20 00 + InstDummy, // 20 01 + InstDummy, // 20 02 + InstDummy, // 20 03 + InstDummy, // 20 04 + InstDummy, // 20 05 + InstDummy, // 20 06 + InstDummy, // 20 07 + InstDummy, // 20 08 + InstDummy, // 20 09 + InstDummy, // 20 0A + InstDummy, // 20 0B + InstDummy, // 20 0C + InstDummy, // 20 0D + InstDummy, // 20 0E + InstDummy, // 20 0F + InstUnk2010CCLCC, // 20 10 + InstUnk2011CCLCC, // 20 11 + InstUnk2012CCLCC, // 20 12 + InstDummy, // 20 13 + InstDummy, // 20 14 + InstDummy, // 20 15 + InstDummy, // 20 16 + InstDummy, // 20 17 + InstDummy, // 20 18 + InstDummy, // 20 19 + InstDummy, // 20 1A + InstDummy, // 20 1B + InstDummy, // 20 1C + InstDummy, // 20 1D + InstDummy, // 20 1E + InstDummy, // 20 1F + InstDummy, // 20 20 + InstDummy, // 20 21 + InstDummy, // 20 22 + InstDummy, // 20 23 + InstDummy, // 20 24 + InstDummy, // 20 25 + InstDummy, // 20 26 + InstDummy, // 20 27 + InstDummy, // 20 28 + InstDummy, // 20 29 + InstDummy, // 20 2A + InstDummy, // 20 2B + InstDummy, // 20 2C + InstDummy, // 20 2D + InstDummy, // 20 2E + InstDummy, // 20 2F + InstDummy, // 20 30 + InstDummy, // 20 31 + InstDummy, // 20 32 + InstDummy, // 20 33 + InstDummy, // 20 34 + InstDummy, // 20 35 + InstDummy, // 20 36 + InstDummy, // 20 37 + InstDummy, // 20 38 + InstDummy, // 20 39 + InstDummy, // 20 3A + InstDummy, // 20 3B + InstDummy, // 20 3C + InstDummy, // 20 3D + InstDummy, // 20 3E + InstDummy, // 20 3F + InstDummy, // 20 40 + InstDummy, // 20 41 + InstDummy, // 20 42 + InstDummy, // 20 43 + InstDummy, // 20 44 + InstDummy, // 20 45 + InstDummy, // 20 46 + InstDummy, // 20 47 + InstDummy, // 20 48 + InstDummy, // 20 49 + InstDummy, // 20 4A + InstDummy, // 20 4B + InstDummy, // 20 4C + InstDummy, // 20 4D + InstDummy, // 20 4E + InstDummy, // 20 4F + InstDummy, // 20 50 + InstDummy, // 20 51 + InstDummy, // 20 52 + InstDummy, // 20 53 + InstDummy, // 20 54 + InstDummy, // 20 55 + InstDummy, // 20 56 + InstDummy, // 20 57 + InstDummy, // 20 58 + InstDummy, // 20 59 + InstDummy, // 20 5A + InstDummy, // 20 5B + InstDummy, // 20 5C + InstDummy, // 20 5D + InstDummy, // 20 5E + InstDummy, // 20 5F + InstDummy, // 20 60 + InstDummy, // 20 61 + InstDummy, // 20 62 + InstDummy, // 20 63 + InstDummy, // 20 64 + InstDummy, // 20 65 + InstDummy, // 20 66 + InstDummy, // 20 67 + InstDummy, // 20 68 + InstDummy, // 20 69 + InstDummy, // 20 6A + InstDummy, // 20 6B + InstDummy, // 20 6C + InstDummy, // 20 6D + InstDummy, // 20 6E + InstDummy, // 20 6F + InstDummy, // 20 70 + InstDummy, // 20 71 + InstDummy, // 20 72 + InstDummy, // 20 73 + InstDummy, // 20 74 + InstDummy, // 20 75 + InstDummy, // 20 76 + InstDummy, // 20 77 + InstDummy, // 20 78 + InstDummy, // 20 79 + InstDummy, // 20 7A + InstDummy, // 20 7B + InstDummy, // 20 7C + InstDummy, // 20 7D + InstDummy, // 20 7E + InstDummy, // 20 7F + InstDummy, // 20 80 + InstDummy, // 20 81 + InstDummy, // 20 82 + InstDummy, // 20 83 + InstDummy, // 20 84 + InstDummy, // 20 85 + InstDummy, // 20 86 + InstDummy, // 20 87 + InstDummy, // 20 88 + InstDummy, // 20 89 + InstDummy, // 20 8A + InstDummy, // 20 8B + InstDummy, // 20 8C + InstDummy, // 20 8D + InstDummy, // 20 8E + InstDummy, // 20 8F + InstDummy, // 20 90 + InstDummy, // 20 91 + InstDummy, // 20 92 + InstDummy, // 20 93 + InstDummy, // 20 94 + InstDummy, // 20 95 + InstDummy, // 20 96 + InstDummy, // 20 97 + InstDummy, // 20 98 + InstDummy, // 20 99 + InstDummy, // 20 9A + InstDummy, // 20 9B + InstDummy, // 20 9C + InstDummy, // 20 9D + InstDummy, // 20 9E + InstDummy, // 20 9F + InstDummy, // 20 A0 + InstDummy, // 20 A1 + InstDummy, // 20 A2 + InstDummy, // 20 A3 + InstDummy, // 20 A4 + InstDummy, // 20 A5 + InstDummy, // 20 A6 + InstDummy, // 20 A7 + InstDummy, // 20 A8 + InstDummy, // 20 A9 + InstDummy, // 20 AA + InstDummy, // 20 AB + InstDummy, // 20 AC + InstDummy, // 20 AD + InstDummy, // 20 AE + InstDummy, // 20 AF + InstDummy, // 20 B0 + InstDummy, // 20 B1 + InstDummy, // 20 B2 + InstDummy, // 20 B3 + InstDummy, // 20 B4 + InstDummy, // 20 B5 + InstDummy, // 20 B6 + InstDummy, // 20 B7 + InstDummy, // 20 B8 + InstDummy, // 20 B9 + InstDummy, // 20 BA + InstDummy, // 20 BB + InstDummy, // 20 BC + InstDummy, // 20 BD + InstDummy, // 20 BE + InstDummy, // 20 BF + InstDummy, // 20 C0 + InstDummy, // 20 C1 + InstDummy, // 20 C2 + InstDummy, // 20 C3 + InstDummy, // 20 C4 + InstDummy, // 20 C5 + InstDummy, // 20 C6 + InstDummy, // 20 C7 + InstDummy, // 20 C8 + InstDummy, // 20 C9 + InstDummy, // 20 CA + InstDummy, // 20 CB + InstDummy, // 20 CC + InstDummy, // 20 CD + InstDummy, // 20 CE + InstDummy, // 20 CF + InstDummy, // 20 D0 + InstDummy, // 20 D1 + InstDummy, // 20 D2 + InstDummy, // 20 D3 + InstDummy, // 20 D4 + InstDummy, // 20 D5 + InstDummy, // 20 D6 + InstDummy, // 20 D7 + InstDummy, // 20 D8 + InstDummy, // 20 D9 + InstDummy, // 20 DA + InstDummy, // 20 DB + InstDummy, // 20 DC + InstDummy, // 20 DD + InstDummy, // 20 DE + InstDummy, // 20 DF + InstDummy, // 20 E0 + InstDummy, // 20 E1 + InstDummy, // 20 E2 + InstDummy, // 20 E3 + InstDummy, // 20 E4 + InstDummy, // 20 E5 + InstDummy, // 20 E6 + InstDummy, // 20 E7 + InstDummy, // 20 E8 + InstDummy, // 20 E9 + InstDummy, // 20 EA + InstDummy, // 20 EB + InstDummy, // 20 EC + InstDummy, // 20 ED + InstDummy, // 20 EE + InstDummy, // 20 EF + InstDummy, // 20 F0 + InstDummy, // 20 F1 + InstDummy, // 20 F2 + InstDummy, // 20 F3 + InstDummy, // 20 F4 + InstDummy, // 20 F5 + InstDummy, // 20 F6 + InstDummy, // 20 F7 + InstDummy, // 20 F8 + InstDummy, // 20 F9 + InstDummy, // 20 FA + InstDummy, // 20 FB + InstDummy, // 20 FC + InstDummy, // 20 FD + InstDummy, // 20 FE + InstDummy // 20 FF +}; + + +} + +} + +// clang-format on \ No newline at end of file diff --git a/src/vm/sc3stream.h b/src/vm/sc3stream.h index d35836a69..f0a24552c 100644 --- a/src/vm/sc3stream.h +++ b/src/vm/sc3stream.h @@ -9,8 +9,10 @@ namespace Vm { class Sc3Stream { public: + Sc3Stream(uint32_t* ptr) : Ptr(reinterpret_cast(ptr)) {} Sc3Stream(uint16_t* ptr) : Ptr(reinterpret_cast(ptr)) {} Sc3Stream(uint8_t* ptr) : Ptr(ptr) {} + Sc3Stream(std::nullptr_t ptr) : Ptr(ptr) {} uint8_t ReadU8() { uint8_t result = PeekU8(); Advance(1); @@ -26,6 +28,19 @@ class Sc3Stream { auto adjOffset = offset * sizeof(uint16_t); return (PeekU8(adjOffset + 1) << 8) | PeekU8(adjOffset); } + + uint32_t ReadU32() { + uint32_t result = PeekU32(); + Advance(4); + return result; + } + + uint32_t PeekU32(int64_t offset = 0) const { + auto adjOffset = offset * sizeof(uint32_t); + return (PeekU8(adjOffset + 3) << 24) | (PeekU8(adjOffset + 2) << 16) | + (PeekU8(adjOffset + 1) << 8) | PeekU8(adjOffset); + } + uint8_t PeekU8(int64_t offset = 0) const { return *(Ptr + offset); }; uint8_t* Data() const { return Ptr; } void Advance(int64_t n) { Ptr += n; } diff --git a/src/vm/thread.cpp b/src/vm/thread.cpp index 9a7885668..7e8a0d8c4 100644 --- a/src/vm/thread.cpp +++ b/src/vm/thread.cpp @@ -53,11 +53,31 @@ void Sc3VmThread::SetIp(uint8_t* ptr) { ScriptBuffers[ScriptBufferId].size()); IpOffset = static_cast(ptr - ScriptBuffers[ScriptBufferId].data()); } - void Sc3VmThread::SetIp(BufferOffsetContext ctx) { - assert(ctx.IpOffset < ScriptBuffers[ctx.ScriptBufferId].size()); + assert(ctx.IpOffset < ScriptBuffers[ctx.BufferId].size()); + IpOffset = ctx.IpOffset; + ScriptBufferId = ctx.BufferId; +} +uint8_t* Sc3VmThread::GetStringIp() const { + auto const& buffer = UseMSBBuffers ? MsbBuffers[ScriptBufferId] + : ScriptBuffers[ScriptBufferId]; + return &buffer[IpOffset]; +} +void Sc3VmThread::SetStringIp(uint8_t* ptr) { + auto const& buffer = UseMSBBuffers ? MsbBuffers[ScriptBufferId] + : ScriptBuffers[ScriptBufferId]; + assert(ptr >= buffer.data() && ptr < buffer.data() + buffer.size()); + IpOffset = static_cast(ptr - buffer.data()); +} + +void Sc3VmThread::SetStringIp(BufferOffsetContext ctx) { + UseMSBBuffers = + ctx.Buffers[ctx.BufferId].data() == MsbBuffers[ctx.BufferId].data(); + assert(ctx.IpOffset < (UseMSBBuffers ? MsbBuffers[ScriptBufferId] + : ScriptBuffers[ScriptBufferId]) + .size()); IpOffset = ctx.IpOffset; - ScriptBufferId = ctx.ScriptBufferId; + ScriptBufferId = ctx.BufferId; } } // namespace Vm diff --git a/src/vm/thread.h b/src/vm/thread.h index aeabcd7d2..fb360e107 100644 --- a/src/vm/thread.h +++ b/src/vm/thread.h @@ -47,7 +47,8 @@ enum ThreadMemberOffset { }; struct BufferOffsetContext { - uint32_t ScriptBufferId; + std::span> Buffers; + uint32_t BufferId; uint32_t IpOffset; }; @@ -80,11 +81,15 @@ struct Sc3VmThread { uint32_t Temp2; uint32_t Variables[MaxThreadVars]; uint32_t DialoguePageId; + bool UseMSBBuffers = false; void* GetMemberPointer(uint32_t offset); uint8_t* GetIp() const; + uint8_t* GetStringIp() const; void SetIp(uint8_t* ptr); void SetIp(BufferOffsetContext ctx); + void SetStringIp(uint8_t* ptr); + void SetStringIp(BufferOffsetContext ctx); }; } // namespace Vm diff --git a/src/vm/vm.cpp b/src/vm/vm.cpp index b55e5ed72..e0ec54450 100644 --- a/src/vm/vm.cpp +++ b/src/vm/vm.cpp @@ -18,6 +18,7 @@ #include "opcodetables_cc.h" #include "opcodetables_sgps3.h" #include "opcodetables_chn.h" +#include "opcodetables_lccswitch.h" #include "../profile/game.h" #include "../profile/vm.h" #include "../profile/scriptinput.h" @@ -52,6 +53,9 @@ static const InstructionProc* OpcodeTableUser1; static const InstructionProc* OpcodeTableGraph; static const InstructionProc* OpcodeTableGraph3D; +// Present in newer engine versions +static const InstructionProc* OpcodeTableTitleOnly = nullptr; + static void CreateThreadExecTable(); static void SortThreadExecTable(); static void CreateThreadDrawTable(); @@ -131,6 +135,14 @@ void Init() { OpcodeTableUser1 = OpcodeTableUser1_CHN; break; } + case InstructionSet::LCCSwitch: { + OpcodeTableSystem = OpcodeTableSystem_LCCSwitch; + OpcodeTableGraph = OpcodeTableGraph_LCCSwitch; + OpcodeTableUser1 = OpcodeTableUser1_LCCSwitch; + OpcodeTableTitleOnly = OpcodeTableTitleOnly_LCCSwitch; + + break; + } default: { ImpLog(LogLevel::Fatal, LogChannel::VM, "Unsupported instruction set\n"); Window->Shutdown(); @@ -139,13 +151,13 @@ void Init() { } for (int i = 0; i < MaxThreads - 1; i++) { - memset(&ThreadPool[i], 0, sizeof(Sc3VmThread)); + ThreadPool[i] = {}; ThreadPool[i].NextFreeContext = &ThreadPool[i + 1]; ThreadPool[i].Id = i; } NextFreeThreadCtx = ThreadPool; - memset(&ThreadPool[MaxThreads - 1], 0, sizeof(Sc3VmThread)); + ThreadPool[MaxThreads - 1] = {}; ThreadPool[MaxThreads - 1].Id = MaxThreads - 1; for (int i = 0; i < MaxThreadGroups; i++) { @@ -385,7 +397,7 @@ void DestroyThread(Sc3VmThread* thread) { } --ThreadGroupCount[thread->GroupId]; int id = thread->Id; - memset(thread, 0, sizeof(Sc3VmThread)); + *thread = Sc3VmThread{}; thread->Id = id; thread->NextFreeContext = NextFreeThreadCtx; NextFreeThreadCtx = thread; @@ -489,10 +501,13 @@ void RunThread(Sc3VmThread* thread, float dt) { opcodeGrp1 = opcodeGrp & 0x7F; ImpLog(LogLevel::Trace, LogChannel::VM, - "Address: {:#0x} Opcode: {:02x}:{:02x} ScriptBuffer: {:d}\n", - scriptIp, opcodeGrp1, opcode, thread->ScriptBufferId); + "Address: {:#0x} TID: {:d} Opcode: {:02x}:{:02x} ScriptBuffer: " + "{:d}\n", + scriptIp, thread->Id, opcodeGrp1, opcode, thread->ScriptBufferId); - if (opcodeGrp1 == 0x10) { + if (opcodeGrp1 == 0x20) { + OpcodeTableTitleOnly[opcode](thread, dt); + } else if (opcodeGrp1 == 0x10) { OpcodeTableUser1[opcode](thread, dt); } else if (opcodeGrp1 == 0x02) { OpcodeTableGraph3D[opcode](thread, dt); @@ -568,19 +583,21 @@ uint32_t ScriptGetStrAddress(uint32_t scriptBufferId, uint32_t mesNum) { BufferOffsetContext ScriptGetTextTableStrAddress(uint32_t textTableId, uint32_t strNum) { uint32_t scriptBufferId = TextTable[textTableId].scriptBufferId; - uint32_t stringTableAdrRel = - SDL_Swap32LE(UnalignedRead(&ScriptBuffers[scriptBufferId][4])); - uint8_t* stringTableAdr = - (uint8_t*)&ScriptBuffers[scriptBufferId][stringTableAdrRel]; auto [textScrBufId, labelOffset] = TextTable[textTableId]; uint8_t* textTable = &ScriptBuffers[textScrBufId][labelOffset]; - uint16_t mesNum = - UnalignedRead(&textTable[strNum * sizeof(uint16_t)]); + uint32_t mesNum; + if (Profile::Vm::StringIdSize == 4) { + mesNum = UnalignedRead(&textTable[strNum * sizeof(uint32_t)]); + } else { + mesNum = UnalignedRead(&textTable[strNum * sizeof(uint16_t)]); + } + uint32_t stringAdrRel = Profile::Vm::UseMsbStrings + ? MsbGetStrAddress(scriptBufferId, mesNum) + : ScriptGetStrAddress(scriptBufferId, mesNum); - uint32_t stringAdrRel = SDL_Swap32LE( - UnalignedRead(&stringTableAdr[mesNum * sizeof(uint32_t)])); - return {scriptBufferId, stringAdrRel}; + return {Profile::Vm::UseMsbStrings ? MsbBuffers : ScriptBuffers, + scriptBufferId, stringAdrRel}; } uint32_t ScriptGetRetAddress(uint32_t scriptBufferId, uint32_t retNum) { diff --git a/src/vm/vm.h b/src/vm/vm.h index b312764a7..14010451f 100644 --- a/src/vm/vm.h +++ b/src/vm/vm.h @@ -21,6 +21,7 @@ enum class InstructionSet : int { SGPS3, MO8, CHN, + LCCSwitch, }; using InstructionProc = auto (*)(Sc3VmThread* thread, float dt) -> void; diff --git a/vcpkg.json b/vcpkg.json index fa68cb5d6..6c9e5d207 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -14,6 +14,7 @@ "openal-soft", "libogg", "libvorbis", + "opus", "zlib", { "name": "avcpp",